diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9b0cfaa8..aac5c992 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -32,11 +32,13 @@ jobs: - name: Docker setup uses: docker/setup-buildx-action@v3 - # In order for the app to run in the dev mode we need to set the required env vars even if they aren't actually used by the app. - # This step sets mock env vars in order to pass the validation steps so the app can run - # in the CI environment. For env vars that are actually used in tests and therefore need real values, we set them in - # the GitHub secrets settings and access them in a step below. + # In order for the app to run in the dev mode we need to set the required env vars even if + # they aren't actually used by the app. This step sets mock env vars in order to pass the + # validation steps so the app can run in the CI environment. For env vars that are actually + # used in tests and therefore need real values, we set them in the GitHub secrets settings and + # access them in a step below. - name: Set required wasp app env vars to mock values + working-directory: ./template run: | cd app cp .env.server.example .env.server && cp .env.client.example .env.client @@ -45,17 +47,19 @@ jobs: uses: actions/cache@v4 with: path: ~/.npm - key: node-modules-${{ runner.os }}-${{ hashFiles('app/package-lock.json') }}-${{ hashFiles('e2e-tests/package-lock.json') }}-wasp${{ env.WASP_VERSION }}-node${{ steps.setup-node.outputs.node-version }} + key: node-modules-${{ runner.os }}-${{ hashFiles('template/app/package-lock.json') }}-${{ hashFiles('template/e2e-tests/package-lock.json') }}-wasp${{ env.WASP_VERSION }}-node${{ steps.setup-node.outputs.node-version }} restore-keys: | node-modules-${{ runner.os }}- - + - name: Install Node.js dependencies for Playwright tests if: steps.cache-e2e-tests.outputs.cache-hit != 'true' + working-directory: ./template run: | cd e2e-tests npm ci - name: Store Playwright's Version + working-directory: ./template run: | cd e2e-tests PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//') @@ -68,9 +72,10 @@ jobs: with: path: ~/.cache/ms-playwright key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}-${{ runner.os }} - + - name: Set up Playwright if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' + working-directory: ./template run: | cd e2e-tests npx playwright install --with-deps @@ -79,14 +84,14 @@ jobs: run: | curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list - sudo apt update + sudo apt update sudo apt install stripe - # For Stripe webhooks to work in development, we need to run the Stripe CLI to listen for webhook events. + # For Stripe webhooks to work in development, we need to run the Stripe CLI to listen for webhook events. # The Stripe CLI will receive the webhook events from Stripe test payments and # forward them to our local server so that we can test the payment flow in our e2e tests. - name: Run Stripe CLI to listen for webhooks - env: + env: STRIPE_DEVICE_NAME: ${{ secrets.STRIPE_DEVICE_NAME }} run: | stripe listen --api-key ${{ secrets.STRIPE_KEY }} --forward-to localhost:3001/stripe-webhook & @@ -102,6 +107,7 @@ jobs: PRO_SUBSCRIPTION_PRICE_ID: ${{ secrets.PRO_SUBSCRIPTION_PRICE_ID }} CREDITS_PRICE_ID: ${{ secrets.CREDITS_PRICE_ID }} SKIP_EMAIL_VERIFICATION_IN_DEV: true + working-directory: ./template run: | cd e2e-tests npm run e2e:playwright diff --git a/.github/workflows/retag-commit.yml b/.github/workflows/retag-commit.yml deleted file mode 100644 index 73038734..00000000 --- a/.github/workflows/retag-commit.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Retag Commit - -on: - push: - branches: - - main - -jobs: - retag: - runs-on: ubuntu-latest - env: - TAG_NAME: wasp-v0.13-template - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Configure Git - run: | - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - - - name: Delete Old Tag - run: | - git tag -d ${{ env.TAG_NAME }} || true - git push origin :refs/tags/${{ env.TAG_NAME }} || true - - - name: Add New Tag - run: | - git tag ${{ env.TAG_NAME }} - git push origin ${{ env.TAG_NAME }} diff --git a/.gitignore b/.gitignore index d3e88a94..1df726a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,2 @@ -*/.wasp/ -*/.env.server -*/.env.client -*/node_modules -*/migrations -*/.DS_Store -.DS_Store \ No newline at end of file +# There is no need to pollute the template with the migrations. +template/app/migrations/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1fc64d8..928608e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,38 +6,36 @@ Check if there is a GitHub issue already for the thing you would like to work on Let us know, in the issue, that you would like to work on it and how you plan to approach it. This helps, especially with the more complex issues, as it allows us to discuss the solution upfront and make sure it is well planned and fits with the rest of the project. +## Repo organization + +Repo is divided into two main parts: [template](/template) dir and [opensaas-sh](/opensaas-sh) dir. + +`template` contains the actual open saas template that will be used by Wasp to create your new open-saas-based app when you run `wasp new -t saas`. + +`opensaas-sh` is the app deployed to https://opensaas.sh , and is actually made with open saas! It contains a demo app and open saas docs. We keep it updated as we work on the template. + ## How to Contribute 1. Make sure you understand the basics of how open-saas works (check out [docs](https://docs.opensaas.sh)). -2. Check out this repo (`main` branch) and make sure you are able to get the app in `app/` running (to set it up, follow the same steps as for running a new open-saas app, as explained in the open-saas docs). +2. Check out this repo (`main` branch) and make sure you are able to get the app in [template/app/](/template/app) running (to set it up, follow the same steps as for running a new open-saas app, as explained in the open-saas docs). 3. Create a new git branch for your work (aka feature branch) and do your changes on it. -4. Update e2e tests in [e2e-tests](/e2e-tests/) if needed and make sure they are passing. -5. Create a pull request (towards `main` as a base branch). -6. If docs (also) need updating, check out the `deployed-version` branch, make your own feature branch from it, make changes in [blog/src/content/docs](/blog/src/content/docs/), and submit another PR with those changes (towards `deployed-version` as a base branch). -7. Make a "Da Boi" meme while you wait for us to review your PR(s). -8. If you don't know who "Da Boi" is, head back to the [Wasp Discord](https://discord.gg/aCamt5wCpS) and find out :) +4. Update e2e tests in [template/e2e-tests](/template/e2e-tests/) if needed and make sure they are passing. +5. Update docs in [opensaas-sh/blog/src/content/docs](/opensaas-sh/blog/src/content/docs/) if needed. Check [opensaas-sh/README.md](/opensaas-sh/README.md) for more details. +6. Update demo app in [opensaas-sh/app_diff](/opensaas-sh/app_diff) if needed. Check [opensaas-sh/README.md](/opensaas-sh/README.md) for more details. +7. Create a pull request (towards `main` as a base branch). +8. Make a "Da Boi" meme while you wait for us to review your PR(s). +9. If you don't know who "Da Boi" is, head back to the [Wasp Discord](https://discord.gg/aCamt5wCpS) and find out :) ## Additional Info -### Template Versioning +### Template Versioning Whenever a user starts a new Wasp project with `wasp new -t `, Wasp looks for a specific tag on the template repo, and pulls the project at the commit associated with that tag. In the case of Open SaaS, which is a Wasp template, the tag is `wasp-v{{version}}-template`, where `{{version}}` is the current version of Wasp, e.g. `wasp-v0.13-template`. -For simplicity, in Open SaaS, we automatically re-apply the tag to the most recent commit on the `main` branch via the `.github/workflows/retag-commit.yml` workflow. This way, users always get the latest version of the template (as on `main` branch) when they start a new project via `wasp new -t saas`. +We manually (re)assign the appropriate tag when we are ready to release a new version of Open Saas. -**This means, that whenever a user pulls the template via `wasp new -t saas`, they are getting the version present in the most recent commit on `main`.** - -NOTE: When Wasp releases a new major version, we should also make sure to update Open SaaS to work with this new version. In PR that will bring this update, we should also make sure to update the `TAG_NAME` in the GitHub Workflow that does the tagging, to be the template tag used by the newest version of Wasp. - -### The Default Template vs. the Deployed Site / Docs - -There are two main branches for development: -- `main` -- `deployed-version` - -The default, clean template that users get when cloning the starter lives on `main`, while `deployed-version` is somewhat modified version of that same template which you see when you go to [OpenSaaS.sh](https://opensaas.sh) and the [docs](https://docs.opensaas.sh). - -If you want to make changes to the default starter template, base feature branches and Pull Requests off of `main`. -If you want to make changes to the OpenSaaS.sh site or it's Documentation, base feature branches and Pull Requests off of `deployed-version`. +### Releasing +1. Assign appropriate tag to the commit we want to release (usually current `main`) so `wasp new -t saas` can pick up code from the [/template](/template). +2. Deploy the demo app + landing page and blog + docs: [/opensaas-sh/README.md#Deployment](/opensaas-sh/README.md#Deployment). diff --git a/opensaas-sh/README.md b/opensaas-sh/README.md new file mode 100644 index 00000000..4d05f5a8 --- /dev/null +++ b/opensaas-sh/README.md @@ -0,0 +1,30 @@ +# OpenSaas.sh + +This is the https://opensaas.sh page and demo app, built with the Open Saas template! + +It consists of a Wasp app for showcasing the Open Saas template (+ landing page), while the Astro blog is blog and docs for the Open Saas template, found at https://docs.opensaas.sh. + +Inception :)! + +## Development + +### Demo app (app_diff/) + +Since the demo app is just the open saas template with some small tweaks, and we want to be able to easily keep it up to date as the template changes, we don't version (in git) the actual demo app code, instead we version the diffs between it and the template: `app_diff/`. + +So because we don't version the actual demo app (`app/`) but its diffs instead (`app_diff`), the typical workflow is as follows: +1. Run `./tools/patch.sh` to generate `app/` from `../template/` and `app_diff/`. +2. If there are any conflicts (normally due to updates to the template), modify `app_diff/` till you resolve them. +3. Make any changes in the `app/` if you wish, and then generate new `app_diff/` by running `./tools/diff.sh`. + +Make sure not to commit `app/` to git. It is currently (until we resolve this) not added to .gitignore because that messes up diffing for us. + +### Blog (blog/) + +Blog (and docs in it) is currently tracked in whole, as it has quite some content, so updating it to the latest version of Open Saas is done manually, but it might be interesting to also move it to the `diff` approach, as we use for the demo app, if it turns out to be a good match. + +## Deployment + +App: check its README.md (after you generate it with `.tools/patch.sh`) . + +Blog (docs): hosted on Netlify. diff --git a/opensaas-sh/app_diff/.env.client.diff b/opensaas-sh/app_diff/.env.client.diff new file mode 100644 index 00000000..82bd1ef9 --- /dev/null +++ b/opensaas-sh/app_diff/.env.client.diff @@ -0,0 +1,7 @@ +--- template/app/.env.client ++++ opensaas-sh/app/.env.client +@@ -0,0 +1,3 @@ ++REACT_APP_STRIPE_CUSTOMER_PORTAL=https://billing.stripe.com/p/login/test_8wM8x17JN7DT4zC000 ++ ++REACT_APP_GOOGLE_ANALYTICS_ID=G-H3LSJCK95H +\ No newline at end of file diff --git a/opensaas-sh/app_diff/.env.vault.diff b/opensaas-sh/app_diff/.env.vault.diff new file mode 100644 index 00000000..0d0dc659 --- /dev/null +++ b/opensaas-sh/app_diff/.env.vault.diff @@ -0,0 +1,28 @@ +--- template/app/.env.vault ++++ opensaas-sh/app/.env.vault +@@ -0,0 +1,25 @@ ++#/-------------------.env.vault---------------------/ ++#/ cloud-agnostic vaulting standard / ++#/ [how it works](https://dotenv.org/env-vault) / ++#/--------------------------------------------------/ ++ ++# development ++DOTENV_VAULT_DEVELOPMENT="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" ++DOTENV_VAULT_DEVELOPMENT_VERSION=4 ++ ++# ci ++DOTENV_VAULT_CI="BzXtisw7TpmxCnpJlocNzsueSrO+0NfaJoRWBj7IkQXc5z4WIgnF9f9F98qyYoMWJRj3ij5Ytbt4jMC/LIJTWqUdY8xSnzPA3Wt21OXZre511kiGOMbzdTsJXUN7jDX9XQxNC6aTZ/t7BwNxHi35g3acjn0qxXZDcnzMA+TRtYefuDcLjqMugVKnoL3NKtEuZnepDWgXickRk1yI/QlLYn7z2UKEDu+RTn+LcBfjKwYcSjsGxMphDCdN1zphecd7apVX57PY3zFXXVSTQLOE6EdmJk/bnvhq5meqCcjlXKe3FSAUc+TsvzGG/8P/clGX1lPMD7tIyIJACtvUmtaGinYRwCQaPQiUdYC3BmFSNs8ctH1nElSvGIwdncY3XKh1ryvpZX3pLpFhI9/n+EVt8ncBz7KP883oLzjP+yopRjOj8/T2Isrt+rXlCj4TJtnzs/VNzcokY62xfN9tx8Q+wEvZEeIY7fuVNO5wUyAUlpt2hyfuEf3tSHnT7viLt5fXZhwWc4gntT6HEUsysA49kp31VC48KjVkNsdXv960s8ZmbG/TBaShAD27zrO4G3s20lEpDnzuX+rLVAiRAvxmnAtsB5Odz+E6npoIHN+Wi/W7TXtazQ5IirCwVRLg+zPHaLmovy2ROc0qbDMZXn2M6mrHFjklO3br91u5WBZXhtt7ioP06xSe/FmKnwNFGu17NW5knWXGW/GkLhj+u5juY09ODSpPrbF0Saxp/tlL1ov3pGraXK/83Y88TuBYMtix5dqg75E8OshDrxUnQQEiVmexo9Jrtxbf50J72v3N1YA2ulCYHrcMTitgmjFVPZD0BSQESkcUIP0MHVap39XSg+uAaIHvffnNJZPY" ++DOTENV_VAULT_CI_VERSION=3 ++ ++# staging ++DOTENV_VAULT_STAGING="R8o5+wTbFwi3IpN48pSaw4RwxqThoeUsnBuG3oGF9UTx0HfhpHdaZ4TFlk1KoVJs/XOQl55LmMsiZX07ULeVvJogbKrK/XEyLgWXolbkpL2OEerNaaFwZ+HdPTd6Z9ZgQGtPn3/y+CDG9dVp/uYTF+73SigMhhHNUO701MNirD1A/maDFmuUKZM8RwgmgZsdd8B9pPP9J+55tc5zGd3tI2hz5svdtodirXwBW+q5GGiAYxymu6kzXKY8OtcUzJQO1QDxIgERlybnJj1rBz4/WYYuR2WBRjeUCI47FbjhG0G5P9IM3Vv/p9MN6PDV7l6rnIrT393y3yXb9WFxUqUZdOAeSzC5CVMuW+EJuCmw6+MG7BRczYpiasnYHhgFRWriU4HlE56vtt/L/5wlWvQOZpmiwwBpI8R14sJT8ZJHl0aIr3XX7kjNhXuy1s+nCuKBcnA4Scl428dw70y0crWCQ6ooWY4PbNRISDtErzxXJuLOf9301j8OdGrrBwPfRMY/uK5TzfkIuc7ODQbNXmlX+cczX07SNlF0px3/sXwfeeBHE3nj6rcOnqbu6iVhZ5m68cjluaf4fUFJGDrw5qEGj1iZLw3BmfrBAeLbGkEkgOTdouzzrLBPMgi8W7Vh7zQkb5UkhO3i//i2aTLC2KulAyB3jL4kO+XzjEToO2kWdamr9UwHPP/Ii/XFB7DNWnFio/L8oFe4ZjqztE7jgQlOCtZQhFzu92A5D7h3oQgdhIu3FLxdWzON673zmSFTdJHwP/6UHGGDBFTuh3/cycYOhytibw8PfI4GdYqh4FsJeYb1mrnQV4TFnvwaI6MrsyDyJaK82/OEySccdllsHBHuHi7m29upjCe9cD49" ++DOTENV_VAULT_STAGING_VERSION=3 ++ ++# production ++DOTENV_VAULT_PRODUCTION="WJOcgnA2hoxbckPeYKs0YbaTX+f6wB5c0eEzBvft4xAV4lq45I3Zd6vGksiswVXrDakCQfNYQgIZgCvNLFmCtS4ibZ1S1QUXyFtrH1uqLj3cP8TE0JrVQfzvayWaFZytTgXn2pwDJHKIFGSGq22csUd9TrTuMpeZXbaPgPGy1dFczJ/MDPXRz1ls7bsV/Lie+lzozhmLZZzbVKgw0Gymgtk+oqFuqoO0WFlJeTGYrnZgdg5DUW3LpIsMj+7fpmha8s2MrzL8Npo/W0XkR6ifH3JW9yOUUHVD9iJcRCky+HRFzARD74I3BVWxhJ/uHJrrTU2ArdTDbubnJZKXJiIJixKUquI+44FjiueayGkwEha+JdIO6D7mYFNw4mF2cqRxLiU6gHQXFIFPH65l4vyLmoo77d9//1NEhBXBqjxypqDq9CG02tvm0zskp0Qf62UoA/rdgarFsGsJG1/neqjhgYRPBE6zub4CQZogdGG3X7JG8Xt5jtYqzxln8S79i1zGY2VTVcy8/Qmva95XBqD6ljQLalBXG2qZK1UtAENubZ8OZ4zhI6kAP7Utp+mt3U3bVQceF/qpKCdJER+4SN8QrV5kx8o12D6f0ma2UnuCgpqajXOPVnTKvNmp6So4iXYug4Y+LVirL2m8jpaupDutPPa3CeDAkH4y9yCz8TGLX7pG2+Zp04quZVZN76yecacwsigIfYCsJg3tx/Yv36cJcoON/KlJIW2cZPK6/E8e7yrljk+gRbE6ICSjOwbfcdp0xFmQb1dLARPeILtjsjJ93RCPZV3TeNrMWcNl2Hj/ASCtgHpL48o90UnwEKTHBmu+hogtzd8NRyvUXdXayXT3Tn3rIj1p/Qg052zN" ++DOTENV_VAULT_PRODUCTION_VERSION=3 ++ ++#/----------------settings/metadata-----------------/ ++DOTENV_VAULT="vlt_47e3eeb0730e831e688049600e59f8975260a1f00302ae08684ed87ba67872d0" ++DOTENV_API_URL="https://vault.dotenv.org" ++DOTENV_CLI="npx dotenv-vault@latest" diff --git a/opensaas-sh/app_diff/.gitignore.diff b/opensaas-sh/app_diff/.gitignore.diff new file mode 100644 index 00000000..33cfff7a --- /dev/null +++ b/opensaas-sh/app_diff/.gitignore.diff @@ -0,0 +1,31 @@ +--- template/app/.gitignore ++++ opensaas-sh/app/.gitignore +@@ -2,16 +2,24 @@ + node_modules/ + **/.DS_Store + +-# We ignore env files recognized and used by Wasp. ++# We ignore sensitive env files recognized and used by Wasp. + .env.server +-.env.client +- + # To be extra safe, we by default ignore any files with `.env` extension in them. + # If this is too agressive for you, consider allowing specific files with `!` operator, + # or modify/delete these two lines. + *.env + *.env.* +- ++# These two we added only because dotenv-vault keeps adding them if it doesn't find them, ++# even though we don't need them. Remove them once dotenv-vault stops doing that. ++.env* ++.flaskenv* + # We don't want to ignore .env example files. + !*.env.*.example + !*.env.example ++# We don't want to ignore .env.client as it doesn't have any secrets. ++!.env.client ++# These are config files for dotenv-vault, so we don't want to ignore them. ++!.env.project ++!.env.vault ++# .env.me is used by dotenv-vault as a personal credential, so we explicitly ignore it. ++.env.me diff --git a/opensaas-sh/app_diff/README.md.diff b/opensaas-sh/app_diff/README.md.diff new file mode 100644 index 00000000..bbe359bd --- /dev/null +++ b/opensaas-sh/app_diff/README.md.diff @@ -0,0 +1,30 @@ +--- template/app/README.md ++++ opensaas-sh/app/README.md +@@ -1,12 +1,25 @@ +-# ++# opensaas.sh (demo) app + +-Built with [Wasp](https://wasp-lang.dev), based on the [Open Saas](https://opensaas.sh) template. ++This is a Wasp app based on Open Saas template with minimal modifications that make it into a demo app that showcases Open Saas's abilities. ++ ++It is deployed to https://opensaas.sh and serves both as a landing page for Open Saas and as a demo app. + + ## Development + ++### .env files ++`.env.client` file is versioned, but `.env.server` file you have to obtain by running `npm run env:pull`, since it has secrets in it. ++This will generate `.env.server` based on the `.env.vault`. ++We are using https://vault.dotenv.org to power this and have an account/organization up there. ++If you modify .env.server and want to persist the changes (for yourself and for the other team members), do `npm run env:push`. ++ + ### Running locally + - Make sure you have the `.env.client` and `.env.server` files with correct dev values in the root of the project. + - Run the database with `wasp start db` and leave it running. + - Run `wasp start` and leave it running. + - [OPTIONAL]: If this is the first time starting the app, or you've just made changes to your entities/prisma schema, also run `wasp db migrate-dev`. + ++## Deployment ++ ++This app is deployed to fly.io, Wasp org, via `wasp deploy`. ++ ++You can run `npm run deploy` to deploy it via `wasp deploy` with required client side env vars correctly set. diff --git a/opensaas-sh/app_diff/deletions b/opensaas-sh/app_diff/deletions new file mode 100644 index 00000000..115aa784 --- /dev/null +++ b/opensaas-sh/app_diff/deletions @@ -0,0 +1,3 @@ +public/public-banner.png +src/client/static/avatar-placeholder.png +src/client/static/open-saas-banner.png diff --git a/opensaas-sh/app_diff/fly-client.toml.diff b/opensaas-sh/app_diff/fly-client.toml.diff new file mode 100644 index 00000000..9b7d4533 --- /dev/null +++ b/opensaas-sh/app_diff/fly-client.toml.diff @@ -0,0 +1,26 @@ +--- template/app/fly-client.toml ++++ opensaas-sh/app/fly-client.toml +@@ -0,0 +1,22 @@ ++# fly.toml app configuration file generated for open-saas-wasp-sh-client on 2023-12-04T12:34:07+01:00 ++# ++# See https://fly.io/docs/reference/configuration/ for information about how to use this file. ++# ++ ++app = "open-saas-wasp-sh-client" ++primary_region = "ams" ++ ++[build] ++ ++[http_service] ++ internal_port = 8043 ++ force_https = true ++ auto_stop_machines = true ++ auto_start_machines = true ++ min_machines_running = 0 ++ processes = ["app"] ++ ++[[vm]] ++ cpu_kind = "shared" ++ cpus = 1 ++ memory_mb = 1024 +\ No newline at end of file diff --git a/opensaas-sh/app_diff/fly-server.toml.diff b/opensaas-sh/app_diff/fly-server.toml.diff new file mode 100644 index 00000000..e3718a9f --- /dev/null +++ b/opensaas-sh/app_diff/fly-server.toml.diff @@ -0,0 +1,26 @@ +--- template/app/fly-server.toml ++++ opensaas-sh/app/fly-server.toml +@@ -0,0 +1,22 @@ ++# fly.toml app configuration file generated for open-saas-wasp-sh-server on 2023-12-04T12:33:59+01:00 ++# ++# See https://fly.io/docs/reference/configuration/ for information about how to use this file. ++# ++ ++app = "open-saas-wasp-sh-server" ++primary_region = "ams" ++ ++[build] ++ ++[http_service] ++ internal_port = 8080 ++ force_https = true ++ auto_stop_machines = true ++ auto_start_machines = true ++ min_machines_running = 1 ++ processes = ["app"] ++ ++[[vm]] ++ cpu_kind = "shared" ++ cpus = 1 ++ memory_mb = 1024 +\ No newline at end of file diff --git a/opensaas-sh/app_diff/main.wasp.diff b/opensaas-sh/app_diff/main.wasp.diff new file mode 100644 index 00000000..30b299a0 --- /dev/null +++ b/opensaas-sh/app_diff/main.wasp.diff @@ -0,0 +1,99 @@ +--- template/app/main.wasp ++++ opensaas-sh/app/main.wasp +@@ -3,24 +3,27 @@ + version: "^0.13.2" + }, + +- title: "My Open SaaS App", ++ title: "Open SaaS", + + head: [ + "", +- "", ++ "", + "", +- "", +- "", +- "", ++ "", ++ "", ++ ++ "", ++ "", ++ "", ++ "", + "", + "", + "", +- // TODO: You can put your analytics scripts below (https://docs.opensaas.sh/guides/analytics/): +- // If you are going with Plausible: +- "", // for production +- "", // for development +- // If you are going with Google Analytics: +- "" // for both production and development ++ ++ "", ++ "", ++ ++ "" + ], + + // 🔐 Auth out of the box! https://wasp-lang.dev/docs/auth/overview +@@ -32,7 +35,7 @@ + email: { + fromField: { + name: "Open SaaS App", +- email: "me@example.com" ++ email: "vince@wasp-lang.dev" + }, + emailVerification: { + clientRoute: EmailVerificationRoute, +@@ -44,16 +47,14 @@ + }, + userSignupFields: import { getEmailUserFields } from "@src/server/auth/setUsername.js", + }, +- // Uncomment to enable Google Auth (check https://wasp-lang.dev/docs/auth/social-auth/google for setup instructions): +- // google: { // Guide for setting up Auth via Google +- // userSignupFields: import { getGoogleUserFields } from "@src/server/auth/setUsername.js", +- // configFn: import { getGoogleAuthConfig } from "@src/server/auth/setUsername.js", +- // }, +- // Uncomment to enable GitHub Auth (check https://wasp-lang.dev/docs/auth/social-auth/github for setup instructions): +- // gitHub: { +- // userSignupFields: import { getGitHubUserFields } from "@src/server/auth/setUsername.js", +- // configFn: import { getGitHubAuthConfig } from "@src/server/auth/setUsername.js", +- // }, ++ google: { ++ userSignupFields: import { getGoogleUserFields } from "@src/server/auth/setUsername", ++ configFn: import { getGoogleAuthConfig } from "@src/server/auth/setUsername", ++ }, ++ gitHub: { ++ userSignupFields: import { getGitHubUserFields } from "@src/server/auth/setUsername", ++ configFn: import { getGitHubAuthConfig } from "@src/server/auth/setUsername", ++ }, + }, + onAuthFailedRedirectTo: "/login", + onAuthSucceededRedirectTo: "/demo-app", +@@ -76,11 +77,11 @@ + // NOTE: "Dummy" provider is just for local development purposes. + // Make sure to check the server logs for the email confirmation url (it will not be sent to an address)! + // Once you are ready for production, switch to e.g. "SendGrid" or "MailGun" providers. Check out https://docs.opensaas.sh/guides/email-sending/ . +- provider: Dummy, ++ provider: SendGrid, + defaultFrom: { + name: "Open SaaS App", + // When using a real provider, e.g. SendGrid, you must use the same email address that you configured your account to send out emails with! +- email: "me@example.com" ++ email: "vince@wasp-lang.dev" + }, + }, + } +@@ -97,6 +98,9 @@ + username String? @unique + lastActiveTimestamp DateTime @default(now()) + isAdmin Boolean @default(false) ++ // isMockUser is an extra property for the demo app ensuring that all users can access ++ // the admin dashboard but won't be able to see the other users' data, only mock user data. ++ isMockUser Boolean @default(false) + + stripeId String? + checkoutSessionId String? diff --git a/opensaas-sh/app_diff/migrations/20231213174854_init/migration.sql.diff b/opensaas-sh/app_diff/migrations/20231213174854_init/migration.sql.diff new file mode 100644 index 00000000..461896d5 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20231213174854_init/migration.sql.diff @@ -0,0 +1,121 @@ +--- template/app/migrations/20231213174854_init/migration.sql ++++ opensaas-sh/app/migrations/20231213174854_init/migration.sql +@@ -0,0 +1,118 @@ ++-- CreateTable ++CREATE TABLE "User" ( ++ "id" SERIAL NOT NULL, ++ "email" TEXT, ++ "username" TEXT, ++ "password" TEXT, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "lastActiveTimestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "isEmailVerified" BOOLEAN NOT NULL DEFAULT false, ++ "isMockUser" BOOLEAN NOT NULL DEFAULT false, ++ "isAdmin" BOOLEAN NOT NULL DEFAULT true, ++ "emailVerificationSentAt" TIMESTAMP(3), ++ "passwordResetSentAt" TIMESTAMP(3), ++ "stripeId" TEXT, ++ "checkoutSessionId" TEXT, ++ "hasPaid" BOOLEAN NOT NULL DEFAULT false, ++ "subscriptionTier" TEXT, ++ "subscriptionStatus" TEXT, ++ "sendEmail" BOOLEAN NOT NULL DEFAULT false, ++ "datePaid" TIMESTAMP(3), ++ "credits" INTEGER NOT NULL DEFAULT 3, ++ ++ CONSTRAINT "User_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "SocialLogin" ( ++ "id" TEXT NOT NULL, ++ "provider" TEXT NOT NULL, ++ "providerId" TEXT NOT NULL, ++ "userId" INTEGER NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ ++ CONSTRAINT "SocialLogin_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "GptResponse" ( ++ "id" TEXT NOT NULL, ++ "content" TEXT NOT NULL, ++ "userId" INTEGER NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "updatedAt" TIMESTAMP(3) NOT NULL, ++ ++ CONSTRAINT "GptResponse_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "ContactFormMessage" ( ++ "id" TEXT NOT NULL, ++ "content" TEXT NOT NULL, ++ "userId" INTEGER NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "isRead" BOOLEAN NOT NULL DEFAULT false, ++ "repliedAt" TIMESTAMP(3), ++ ++ CONSTRAINT "ContactFormMessage_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "DailyStats" ( ++ "id" SERIAL NOT NULL, ++ "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "totalViews" INTEGER NOT NULL DEFAULT 0, ++ "prevDayViewsChangePercent" TEXT NOT NULL DEFAULT '0', ++ "userCount" INTEGER NOT NULL DEFAULT 0, ++ "paidUserCount" INTEGER NOT NULL DEFAULT 0, ++ "userDelta" INTEGER NOT NULL DEFAULT 0, ++ "paidUserDelta" INTEGER NOT NULL DEFAULT 0, ++ "totalRevenue" DOUBLE PRECISION NOT NULL DEFAULT 0, ++ "totalProfit" DOUBLE PRECISION NOT NULL DEFAULT 0, ++ ++ CONSTRAINT "DailyStats_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "PageViewSource" ( ++ "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "name" TEXT NOT NULL, ++ "visitors" INTEGER NOT NULL, ++ "dailyStatsId" INTEGER, ++ ++ CONSTRAINT "PageViewSource_pkey" PRIMARY KEY ("date","name") ++); ++ ++-- CreateTable ++CREATE TABLE "Logs" ( ++ "id" SERIAL NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ "message" TEXT NOT NULL, ++ "level" TEXT NOT NULL, ++ ++ CONSTRAINT "Logs_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "SocialLogin_provider_providerId_userId_key" ON "SocialLogin"("provider", "providerId", "userId"); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "DailyStats_date_key" ON "DailyStats"("date"); ++ ++-- AddForeignKey ++ALTER TABLE "SocialLogin" ADD CONSTRAINT "SocialLogin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ++ ++-- AddForeignKey ++ALTER TABLE "GptResponse" ADD CONSTRAINT "GptResponse_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ++ ++-- AddForeignKey ++ALTER TABLE "ContactFormMessage" ADD CONSTRAINT "ContactFormMessage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ++ ++-- AddForeignKey ++ALTER TABLE "PageViewSource" ADD CONSTRAINT "PageViewSource_dailyStatsId_fkey" FOREIGN KEY ("dailyStatsId") REFERENCES "DailyStats"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/opensaas-sh/app_diff/migrations/20240105224550_tasks/migration.sql.diff b/opensaas-sh/app_diff/migrations/20240105224550_tasks/migration.sql.diff new file mode 100644 index 00000000..549fc7a1 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20240105224550_tasks/migration.sql.diff @@ -0,0 +1,17 @@ +--- template/app/migrations/20240105224550_tasks/migration.sql ++++ opensaas-sh/app/migrations/20240105224550_tasks/migration.sql +@@ -0,0 +1,14 @@ ++-- CreateTable ++CREATE TABLE "Task" ( ++ "id" TEXT NOT NULL, ++ "description" TEXT NOT NULL, ++ "time" TEXT NOT NULL DEFAULT '1', ++ "isDone" BOOLEAN NOT NULL DEFAULT false, ++ "userId" INTEGER NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ ++ CONSTRAINT "Task_pkey" PRIMARY KEY ("id") ++); ++ ++-- AddForeignKey ++ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/opensaas-sh/app_diff/migrations/20240207164719_files/migration.sql.diff b/opensaas-sh/app_diff/migrations/20240207164719_files/migration.sql.diff new file mode 100644 index 00000000..901de104 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20240207164719_files/migration.sql.diff @@ -0,0 +1,18 @@ +--- template/app/migrations/20240207164719_files/migration.sql ++++ opensaas-sh/app/migrations/20240207164719_files/migration.sql +@@ -0,0 +1,15 @@ ++-- CreateTable ++CREATE TABLE "File" ( ++ "id" TEXT NOT NULL, ++ "name" TEXT NOT NULL, ++ "type" TEXT NOT NULL, ++ "key" TEXT NOT NULL, ++ "uploadUrl" TEXT NOT NULL, ++ "userId" INTEGER NOT NULL, ++ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, ++ ++ CONSTRAINT "File_pkey" PRIMARY KEY ("id") ++); ++ ++-- AddForeignKey ++ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/opensaas-sh/app_diff/migrations/20240226123357_new_auth_structure/migration.sql.diff b/opensaas-sh/app_diff/migrations/20240226123357_new_auth_structure/migration.sql.diff new file mode 100644 index 00000000..50d61c37 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20240226123357_new_auth_structure/migration.sql.diff @@ -0,0 +1,47 @@ +--- template/app/migrations/20240226123357_new_auth_structure/migration.sql ++++ opensaas-sh/app/migrations/20240226123357_new_auth_structure/migration.sql +@@ -0,0 +1,44 @@ ++-- CreateTable ++CREATE TABLE "Auth" ( ++ "id" TEXT NOT NULL, ++ "userId" INTEGER, ++ ++ CONSTRAINT "Auth_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateTable ++CREATE TABLE "AuthIdentity" ( ++ "providerName" TEXT NOT NULL, ++ "providerUserId" TEXT NOT NULL, ++ "providerData" TEXT NOT NULL DEFAULT '{}', ++ "authId" TEXT NOT NULL, ++ ++ CONSTRAINT "AuthIdentity_pkey" PRIMARY KEY ("providerName","providerUserId") ++); ++ ++-- CreateTable ++CREATE TABLE "Session" ( ++ "id" TEXT NOT NULL, ++ "expiresAt" TIMESTAMP(3) NOT NULL, ++ "userId" TEXT NOT NULL, ++ ++ CONSTRAINT "Session_pkey" PRIMARY KEY ("id") ++); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "Auth_userId_key" ON "Auth"("userId"); ++ ++-- CreateIndex ++CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); ++ ++-- CreateIndex ++CREATE INDEX "Session_userId_idx" ON "Session"("userId"); ++ ++-- AddForeignKey ++ALTER TABLE "Auth" ADD CONSTRAINT "Auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ++ ++-- AddForeignKey ++ALTER TABLE "AuthIdentity" ADD CONSTRAINT "AuthIdentity_authId_fkey" FOREIGN KEY ("authId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; ++ ++-- AddForeignKey ++ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/opensaas-sh/app_diff/migrations/20240226130234_remove_old_auth_structure/migration.sql.diff b/opensaas-sh/app_diff/migrations/20240226130234_remove_old_auth_structure/migration.sql.diff new file mode 100644 index 00000000..e2adb666 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20240226130234_remove_old_auth_structure/migration.sql.diff @@ -0,0 +1,24 @@ +--- template/app/migrations/20240226130234_remove_old_auth_structure/migration.sql ++++ opensaas-sh/app/migrations/20240226130234_remove_old_auth_structure/migration.sql +@@ -0,0 +1,21 @@ ++/* ++ Warnings: ++ ++ - You are about to drop the column `emailVerificationSentAt` on the `User` table. All the data in the column will be lost. ++ - You are about to drop the column `isEmailVerified` on the `User` table. All the data in the column will be lost. ++ - You are about to drop the column `password` on the `User` table. All the data in the column will be lost. ++ - You are about to drop the column `passwordResetSentAt` on the `User` table. All the data in the column will be lost. ++ - You are about to drop the `SocialLogin` table. If the table is not empty, all the data it contains will be lost. ++ ++*/ ++-- DropForeignKey ++ALTER TABLE "SocialLogin" DROP CONSTRAINT "SocialLogin_userId_fkey"; ++ ++-- AlterTable ++ALTER TABLE "User" DROP COLUMN "emailVerificationSentAt", ++DROP COLUMN "isEmailVerified", ++DROP COLUMN "password", ++DROP COLUMN "passwordResetSentAt"; ++ ++-- DropTable ++DROP TABLE "SocialLogin"; diff --git a/opensaas-sh/app_diff/migrations/20240226145340_remove_unique_email_username/migration.sql.diff b/opensaas-sh/app_diff/migrations/20240226145340_remove_unique_email_username/migration.sql.diff new file mode 100644 index 00000000..2548139b --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20240226145340_remove_unique_email_username/migration.sql.diff @@ -0,0 +1,8 @@ +--- template/app/migrations/20240226145340_remove_unique_email_username/migration.sql ++++ opensaas-sh/app/migrations/20240226145340_remove_unique_email_username/migration.sql +@@ -0,0 +1,5 @@ ++-- DropIndex ++DROP INDEX "User_email_key"; ++ ++-- DropIndex ++DROP INDEX "User_username_key"; diff --git a/opensaas-sh/app_diff/migrations/migration_lock.toml.diff b/opensaas-sh/app_diff/migrations/migration_lock.toml.diff new file mode 100644 index 00000000..4cf6d883 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/migration_lock.toml.diff @@ -0,0 +1,7 @@ +--- template/app/migrations/migration_lock.toml ++++ opensaas-sh/app/migrations/migration_lock.toml +@@ -0,0 +1,3 @@ ++# Please do not edit this file manually ++# It should be added in your version-control system (i.e. Git) ++provider = "postgresql" +\ No newline at end of file diff --git a/opensaas-sh/app_diff/package.json.diff b/opensaas-sh/app_diff/package.json.diff new file mode 100644 index 00000000..89a63f58 --- /dev/null +++ b/opensaas-sh/app_diff/package.json.diff @@ -0,0 +1,13 @@ +--- template/app/package.json ++++ opensaas-sh/app/package.json +@@ -1,5 +1,10 @@ + { + "name": "opensaas", ++ "scripts": { ++ "env:pull": "npx dotenv-vault@latest pull development .env.server", ++ "env:push": "npx dotenv-vault@latest push development .env.server", ++ "deploy": "REACT_APP_STRIPE_CUSTOMER_PORTAL=https://billing.stripe.com/p/login/test_8wM8x17JN7DT4zC000 REACT_APP_GOOGLE_ANALYTICS_ID=G-H3LSJCK95H wasp deploy" ++ }, + "dependencies": { + "@aws-sdk/client-s3": "^3.523.0", + "@aws-sdk/s3-request-presigner": "^3.523.0", diff --git a/opensaas-sh/app_diff/src/client/admin/components/UsersTable.tsx.diff b/opensaas-sh/app_diff/src/client/admin/components/UsersTable.tsx.diff new file mode 100644 index 00000000..21a6c574 --- /dev/null +++ b/opensaas-sh/app_diff/src/client/admin/components/UsersTable.tsx.diff @@ -0,0 +1,54 @@ +--- template/app/src/client/admin/components/UsersTable.tsx ++++ opensaas-sh/app/src/client/admin/components/UsersTable.tsx +@@ -11,6 +11,7 @@ + const [email, setEmail] = useState(undefined); + const [isAdminFilter, setIsAdminFilter] = useState(undefined); + const [statusOptions, setStatusOptions] = useState([]); ++ const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false); + const { data, isLoading, error } = useQuery(getPaginatedUsers, { + skip, + emailContains: email, +@@ -26,8 +27,43 @@ + setskip((page - 1) * 10); + }, [page]); + ++ ++ useEffect(() => { ++ try { ++ if (localStorage.getItem('isDemoInfoVisible') === 'false') { ++ // do nothing ++ } else { ++ setIsDemoInfoVisible(true); ++ } ++ } catch (error) { ++ console.error(error); ++ } ++ }, []); ++ ++ const handleDemoInfoClose = () => { ++ try { ++ localStorage.setItem('isDemoInfoVisible', 'false'); ++ setIsDemoInfoVisible(false); ++ } catch (error) { ++ console.error(error); ++ } ++ }; ++ + return ( +
++ {/* Floating Demo Announcement */} ++ {isDemoInfoVisible && ( ++
++
++ ++ You are viewing mock user data only ;) ++ ++ ++
++
++ )} +
+
+ Filters: diff --git a/opensaas-sh/app_diff/src/client/admin/pages/DashboardPage.tsx.diff b/opensaas-sh/app_diff/src/client/admin/pages/DashboardPage.tsx.diff new file mode 100644 index 00000000..aa36091e --- /dev/null +++ b/opensaas-sh/app_diff/src/client/admin/pages/DashboardPage.tsx.diff @@ -0,0 +1,61 @@ +--- template/app/src/client/admin/pages/DashboardPage.tsx ++++ opensaas-sh/app/src/client/admin/pages/DashboardPage.tsx +@@ -1,5 +1,7 @@ + import { type User } from 'wasp/entities'; + import { useQuery, getDailyStats } from 'wasp/client/operations'; ++import { Link } from "wasp/client/router"; ++import { useState, useEffect } from 'react'; + import TotalSignupsCard from '../components/TotalSignupsCard'; + import TotalPageViewsCard from '../components/TotalPaidViewsCard'; + import TotalPayingUsersCard from '../components/TotalPayingUsersCard'; +@@ -10,6 +12,8 @@ + import { useHistory } from 'react-router-dom'; + + const Dashboard = ({ user }: { user: User }) => { ++ const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false); ++ + const history = useHistory(); + if (!user.isAdmin) { + history.push('/'); +@@ -17,8 +21,41 @@ + + const { data: stats, isLoading, error } = useQuery(getDailyStats); + ++ ++ useEffect(() => { ++ try { ++ if (localStorage.getItem('isStripeDemoInfoVisible') === 'false') { ++ // do nothing ++ } else { ++ setIsDemoInfoVisible(true); ++ } ++ } catch (error) { ++ console.error(error); ++ } ++ }, []); ++ ++ const handleDemoInfoClose = () => { ++ try { ++ localStorage.setItem('isStripeDemoInfoVisible', 'false'); ++ setIsDemoInfoVisible(false); ++ } catch (error) { ++ console.error(error); ++ } ++ }; ++ + return ( + ++ {/* Floating Demo Announcement */} ++ {isDemoInfoVisible && ( ++
++
++ This is actual data from Stripe test purchases.
Try out purchasing a test product!
++ ++
++
++ )} +
+ ( ++ calculateTimeLeft() ++ ); ++ ++ function calculateTimeLeft() { ++ const targetDate = '2024-01-30T08:01:00Z'; ++ let diff = new Date(targetDate).getTime() - new Date().getTime(); ++ let timeLeft: TimeLeft | undefined; ++ ++ if (diff > 0) { ++ timeLeft = { ++ hours: Math.floor((diff / (1000 * 60 * 60)) % 24).toString(), ++ minutes: Math.floor((diff / 1000 / 60) % 60).toString(), ++ // make sure seconds are always displayed as two digits, e.g. '02' ++ seconds: Math.floor((diff / 1000) % 60).toString(), ++ }; ++ } ++ if (!!timeLeft) { ++ if (timeLeft.seconds.length === 1) { ++ timeLeft.seconds = '0' + timeLeft.seconds; ++ } ++ if (timeLeft.minutes.length === 1) { ++ timeLeft.minutes = '0' + timeLeft.minutes; ++ } ++ } ++ return timeLeft; ++ } ++ ++ useEffect(() => { ++ const timer = setTimeout(() => { ++ setTimeLeft(calculateTimeLeft()); ++ }, 1000); ++ return () => clearTimeout(timer); ++ }); ++ ++ return ( ++
++ Open SaaS trending on{' '} ++ ++ GitHub ++ ++ ++ 📈 ++
++ ); ++} diff --git a/opensaas-sh/app_diff/src/client/landing-page/LandingPage.tsx.diff b/opensaas-sh/app_diff/src/client/landing-page/LandingPage.tsx.diff new file mode 100644 index 00000000..4d294f59 --- /dev/null +++ b/opensaas-sh/app_diff/src/client/landing-page/LandingPage.tsx.diff @@ -0,0 +1,379 @@ +--- template/app/src/client/landing-page/LandingPage.tsx ++++ opensaas-sh/app/src/client/landing-page/LandingPage.tsx +@@ -1,24 +1,45 @@ + import { Link } from 'wasp/client/router'; + import { useAuth } from 'wasp/client/auth'; +-import { useState } from 'react'; ++import { useState, useEffect } from 'react'; + import { Dialog } from '@headlessui/react'; +-import { AiFillCloseCircle } from 'react-icons/ai'; ++import { AiFillCloseCircle, AiFillGithub } from 'react-icons/ai'; + import { HiBars3 } from 'react-icons/hi2'; + import { BiLogIn } from 'react-icons/bi'; + import logo from '../static/logo.png'; +-import openSaasBanner from '../static/open-saas-banner.png'; +-import { features, navigation, faqs, footerNavigation, testimonials } from './contentSections'; ++import { ++ features, ++ navigation, ++ faqs, ++ footerNavigation, ++ testimonials, ++} from './contentSections'; + import DropdownUser from '../components/DropdownUser'; +-import { DOCS_URL } from '../../shared/constants'; ++import { DOCS_URL, GITHUB_URL } from '../../shared/constants'; + import { UserMenuItems } from '../components/UserMenuItems'; + import DarkModeSwitcher from '../admin/components/DarkModeSwitcher'; + + export default function LandingPage() { + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); +- ++ const [repoInfo, setRepoInfo] = useState(null); ++ + const { data: user, isLoading: isUserLoading } = useAuth(); + +- const NavLogo = () => Your SaaS App; ++ useEffect(() => { ++ const fetchRepoInfo = async () => { ++ try { ++ const response = await fetch( ++ 'https://api.github.com/repos/wasp-lang/open-saas' ++ ); ++ const data = await response.json(); ++ setRepoInfo(data); ++ } catch (error) { ++ console.error('Error fetching repo info', error); ++ } ++ }; ++ fetchRepoInfo(); ++ }, []); ++ ++ const NavLogo = () => Open SaaS App; + + return ( +
+@@ -28,10 +49,10 @@ + +
+@@ -57,14 +78,14 @@ +
+
+ {/* */} +-
++
+
    + +
+ {isUserLoading ? null : !user ? ( + +-
+- Log in ++
++ Try the Demo App +
+ + ) : ( +@@ -73,7 +94,12 @@ +
+
+ +- ++ +
+ +
+@@ -107,8 +133,8 @@ +
+ {isUserLoading ? null : !user ? ( + +-
+- Log in ++
++ Try the Demo App{' '} +
+ + ) : ( +@@ -134,7 +160,8 @@ +
+
+@@ -152,30 +179,54 @@ +
+
+
+-

+- Some cool words about your product ++

++ The free SaaS template with ++ superpowers +

+-

+- With some more exciting words about your product! ++

++ An open-source, feature-rich, full-stack React + NodeJS ++ template that manages features for you. +

+ +
+
+-
+- ++ {/* App screenshot ++ /> */} ++ +
+
+
+@@ -184,38 +235,36 @@ + + {/* Clients section */} +
+-

+- Built with / Used by: ++

++ Built and Ships with +

+ +
+-
+- +- +- +- +- +- +- +- +- +- +-
+-
+- ++ React ++ NodeJS ++ Wasp ++
++ + + +
+-
++ Tailwind CSS ++ Stripe ++
+ + +
+

+- The Best Features ++ 100% Open-Source +

+-

+- Don't work harder. +-
Work smarter. ++

++ No vendor lock-in. ++
Deploy anywhere. +

+
+
+
+ {features.map((feature) => ( +-
+-
+-
+-
{feature.icon}
+-
+- {feature.name} +-
+-
{feature.description}
+-
++ ++
++
++
++
{feature.icon}
++
++ {feature.name} ++
++
{feature.description}
++
++
+ ))} +
+
+@@ -291,10 +354,18 @@ +

{testimonial.quote}

+ +
+- +- ++ ++ +
+-
{testimonial.name}
++
++ {testimonial.name} ++
+
{testimonial.role}
+
+
+@@ -307,20 +378,28 @@ +
+ + {/* FAQ */} +-
++
+

+ Frequently asked questions +

+-
++
+ {faqs.map((faq) => ( +-
+-
++
++
+ {faq.question} +
+-
+-

{faq.answer}

++
++

++ {faq.answer} ++

+ {faq.href && ( +- ++ + Learn more → + + )} +@@ -335,7 +414,7 @@ +
+