mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-08-08 10:24:56 +02:00
fixes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,6 +6,6 @@ fly config/fly-client.toml
|
|||||||
fly config/fly-server.toml
|
fly config/fly-server.toml
|
||||||
fly-client.toml
|
fly-client.toml
|
||||||
fly-server.toml
|
fly-server.toml
|
||||||
# TODO: change this before deploy
|
# TODO: create a clean version for the user to fill in
|
||||||
# replace with your own Google Analytics service account json file
|
# replace with your own Google Analytics service account json file
|
||||||
saastemplate-381911-6dc3caae2204.json
|
saastemplate-381911-6dc3caae2204.json
|
||||||
|
@@ -7,10 +7,7 @@ export default defineConfig({
|
|||||||
integrations: [
|
integrations: [
|
||||||
starlightBlog({
|
starlightBlog({
|
||||||
title: 'The Best Blog Ever',
|
title: 'The Best Blog Ever',
|
||||||
sidebar: [
|
sidebar: [{}],
|
||||||
{
|
|
||||||
}
|
|
||||||
],
|
|
||||||
authors: {
|
authors: {
|
||||||
vince: {
|
vince: {
|
||||||
name: 'Vince',
|
name: 'Vince',
|
||||||
@@ -22,10 +19,8 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
starlight({
|
starlight({
|
||||||
title: 'Open SaaS Docs',
|
title: 'Open SaaS Docs',
|
||||||
// root: '/start/introdcution/',
|
|
||||||
// site: 'https://www.my-site.dev', // TODO: Change this to your site
|
|
||||||
editLink: {
|
editLink: {
|
||||||
baseUrl: 'https://github.com/withastro/starlight/edit/main/', // TODO: change
|
baseUrl: 'https://github.com/wasp-lang/open-saas/edit/main',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MarkdownContent: 'starlight-blog/overrides/MarkdownContent.astro',
|
MarkdownContent: 'starlight-blog/overrides/MarkdownContent.astro',
|
||||||
@@ -33,7 +28,7 @@ export default defineConfig({
|
|||||||
ThemeSelect: 'starlight-blog/overrides/ThemeSelect.astro',
|
ThemeSelect: 'starlight-blog/overrides/ThemeSelect.astro',
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
github: 'https://github.com/wasp-lang',
|
github: 'https://github.com/wasp-lang/open-saas',
|
||||||
twitter: 'https://twitter.com/wasp_lang',
|
twitter: 'https://twitter.com/wasp_lang',
|
||||||
discord: 'https://discord.gg/aCamt5wCpS',
|
discord: 'https://discord.gg/aCamt5wCpS',
|
||||||
},
|
},
|
||||||
|
@@ -50,9 +50,8 @@ The Admin dashboard is a single place for you to view your most important metric
|
|||||||
|
|
||||||
For a guide on how to integrate these services, check out the [Stripe](/guides/stripe-integration) and [Analytics guide](/guides/analytics) of the docs.
|
For a guide on how to integrate these services, check out the [Stripe](/guides/stripe-integration) and [Analytics guide](/guides/analytics) of the docs.
|
||||||
|
|
||||||
<!-- TODO: add repo links -->
|
|
||||||
:::tip[Help us improve]
|
:::tip[Help us improve]
|
||||||
We're always looking to improve the Admin dashboard. If you feel something is missing or could be improved, consider [opening an issue]() or [submitting a pull request]()
|
We're always looking to improve the Admin dashboard. If you feel something is missing or could be improved, consider [opening an issue](https://github.com/wasp-lang/open-saas/issues) or [submitting a pull request](https://github.com/wasp-lang/open-saas/pulls)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Users
|
### Users
|
||||||
|
@@ -127,7 +127,7 @@ entity User {=psl
|
|||||||
//...
|
//...
|
||||||
psl=}
|
psl=}
|
||||||
```
|
```
|
||||||
// TODO: add screenshot of user table
|
<!-- TODO: add screenshot of user table -->
|
||||||
|
|
||||||
As an Admin, a user has access to the Admin dashboard, along with the user table where they can view and search for users, and edit and update information manually if necessary.
|
As an Admin, a user has access to the Admin dashboard, along with the user table where they can view and search for users, and edit and update information manually if necessary.
|
||||||
|
|
||||||
|
@@ -8,7 +8,36 @@ Google Analytics is free, but tends to be more cumbersome to use.
|
|||||||
Plausible is an open-source, privacy-friendly alternative to Google Analytics. It's also easier to use than Google if you use their hosted service, which is a paid feature. But, it is completely free if you want to self-host it, although this comes with some additional setup steps.
|
Plausible is an open-source, privacy-friendly alternative to Google Analytics. It's also easier to use than Google if you use their hosted service, which is a paid feature. But, it is completely free if you want to self-host it, although this comes with some additional setup steps.
|
||||||
|
|
||||||
## Plausible
|
## Plausible
|
||||||
<!-- TODO add plausible -->
|
|
||||||
|
### Hosted Plausible
|
||||||
|
Sign up for a hosted Plausible account [here](https://plausible.io/).
|
||||||
|
|
||||||
|
Once you've signed up, you'll be taken to your dashboard. Create your site by adding your domain. Your domain is also your `PLAUSIBLE_SITE_ID` in your `.env.server` file. Make sure to add it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PLAUSIBLE_SITE_ID=<your domain without www>
|
||||||
|
```
|
||||||
|
|
||||||
|
After adding your domain, you'll be taken to a page with your Plausible script tag. Copy and paste this script tag into the `main.wasp` file's head section.
|
||||||
|
|
||||||
|
```js {7}
|
||||||
|
app SaaSTemplate {
|
||||||
|
wasp: {
|
||||||
|
version: "^0.11.6"
|
||||||
|
},
|
||||||
|
title: "My SaaS App",
|
||||||
|
head: [
|
||||||
|
"<your plausible script tag here>",
|
||||||
|
],
|
||||||
|
//...
|
||||||
|
```
|
||||||
|
|
||||||
|
Go back to your Plausible dashboard, click on your username in the top right, and click on the `Settings` tab. Scroll down, find your API key and paste it into your `.env.server` file under the `PLAUSIBLE_API_KEY` variable.
|
||||||
|
|
||||||
|
|
||||||
|
### Self-hosted Plausible
|
||||||
|
|
||||||
|
Plausible, being an open-source project, allows you to self-host your analytics. This is a great option if you want to keep your data private and not pay for the hosted service.
|
||||||
|
|
||||||
*coming soon...*
|
*coming soon...*
|
||||||
*until then, check out the [official documentation](https://plausible.io/docs)*
|
*until then, check out the [official documentation](https://plausible.io/docs)*
|
||||||
@@ -25,7 +54,7 @@ After you sign up for [Google analytics](https://analytics.google.com/), go to y
|
|||||||
|
|
||||||
Once you've completed the steps to create a new Property, some Installation Instructions will pop up. Select `install manually` and copy and paste the Google script tag into the `main.wasp` file's head section.
|
Once you've completed the steps to create a new Property, some Installation Instructions will pop up. Select `install manually` and copy and paste the Google script tag into the `main.wasp` file's head section.
|
||||||
|
|
||||||
```js
|
```js {7}
|
||||||
app SaaSTemplate {
|
app SaaSTemplate {
|
||||||
wasp: {
|
wasp: {
|
||||||
version: "^0.11.6"
|
version: "^0.11.6"
|
||||||
@@ -52,7 +81,7 @@ Then, set up the Google Analytics API access by following these steps:
|
|||||||
- Select "JSON", then click “Create” to download your new service account’s JSON key file. Keep this file secure and don't add it to your git repo – it grants access to your Google Analytics data.
|
- Select "JSON", then click “Create” to download your new service account’s JSON key file. Keep this file secure and don't add it to your git repo – it grants access to your Google Analytics data.
|
||||||
5. **Update your Google Anayltics Settings:** Go back to your Google Analytics dashboard, and click on the `Admin` section in the left sidebar. Under `Property Settings > Property > Property Access Management` Add the service account email address (the one that ends with `@your-project-id.iam.gserviceaccount.com`) and give it `Viewer` permissions.
|
5. **Update your Google Anayltics Settings:** Go back to your Google Analytics dashboard, and click on the `Admin` section in the left sidebar. Under `Property Settings > Property > Property Access Management` Add the service account email address (the one that ends with `@your-project-id.iam.gserviceaccount.com`) and give it `Viewer` permissions.
|
||||||
|
|
||||||
6. **Encode and add the environment variables:** Add the `client_email` and the `private_key` from your JSON Key file into your `.env.server` file. But be careful! Because Google uses a special PEM private key, you need to first convert the key to base64, otherwise you will run into errors parsing the key. To do this, in a terminal window, run the command below and paste the output into your `.env.server` file under the `GOOGLE_ANALYTICS_PRIVATE_KEY` variable:
|
6. **Encode and add the Credentials:** Add the `client_email` and the `private_key` from your JSON Key file into your `.env.server` file. But be careful! Because Google uses a special PEM private key, you need to first convert the key to base64, otherwise you will run into errors parsing the key. To do this, in a terminal window, run the command below and paste the output into your `.env.server` file under the `GOOGLE_ANALYTICS_PRIVATE_KEY` variable:
|
||||||
```sh
|
```sh
|
||||||
echo -n "PRIVATE_KEY" | base64
|
echo -n "PRIVATE_KEY" | base64
|
||||||
```
|
```
|
||||||
|
@@ -14,9 +14,8 @@ This template is:
|
|||||||
2. completely free to use and distribute
|
2. completely free to use and distribute
|
||||||
3. comes with a ton of features out of the box!
|
3. comes with a ton of features out of the box!
|
||||||
|
|
||||||
<!-- // TODO: add links -->
|
Try it out here: [OpenSaaS.sh](https://opensaas.sh)
|
||||||
Try it out here:
|
Check Out the Code: Open SaaS [GitHub Repo](https://github.com/wasp-lang/open-saas)
|
||||||
Check Out the Code:
|
|
||||||
|
|
||||||
## What's inside?
|
## What's inside?
|
||||||
|
|
||||||
@@ -44,8 +43,7 @@ You also get access to Wasp's diverse, helpful community if you get stuck or nee
|
|||||||
:::tip["Work In Progress"]
|
:::tip["Work In Progress"]
|
||||||
We've tried to get as many of the core features of a SaaS app into this template as possible, but there still might be some missing features or functionality.
|
We've tried to get as many of the core features of a SaaS app into this template as possible, but there still might be some missing features or functionality.
|
||||||
|
|
||||||
We could always use some help tying up loose ends, so consider [contributing]()!
|
We could always use some help tying up loose ends, so consider [contributing](https://github.com/wasp-lang/open-saas/blob/main/CONTRIBUTING.md)!
|
||||||
<!-- {/* TODO: add link */} -->
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Getting acquainted with the codebase
|
## Getting acquainted with the codebase
|
||||||
|
@@ -18,11 +18,12 @@ Also install the [Wasp extension for VSCode](https://marketplace.visualstudio.co
|
|||||||
|
|
||||||
Clone this repo by running this command in your terminal:
|
Clone this repo by running this command in your terminal:
|
||||||
```sh
|
```sh
|
||||||
git clone // TODO ADD REPO LINK
|
git clone https://github.com/wasp-lang/open-saas.git
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then position yourself in the root of the project:
|
||||||
```sh
|
```sh
|
||||||
cd // TODO ADD REPO NAME
|
cd open-saas
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start your DB
|
### Start your DB
|
||||||
|
12
main.wasp
12
main.wasp
@@ -5,10 +5,10 @@ app SaaSTemplate {
|
|||||||
title: "My Open SaaS App",
|
title: "My Open SaaS App",
|
||||||
head: [
|
head: [
|
||||||
"<meta property='og:type' content='website' />",
|
"<meta property='og:type' content='website' />",
|
||||||
"<meta property='og:url' content='https://mySaaSapp.com' />", // TODO change url
|
"<meta property='og:url' content='https://opensaas.sh' />",
|
||||||
"<meta property='og:description' content='I made a SaaS App. Buy my stuff.' />",
|
"<meta property='og:description' content='I made a SaaS App. Buy my stuff.' />",
|
||||||
"<meta property='og:image' content='src/client/static/image.png' />",
|
"<meta property='og:image' content='https://opensaas.sh/public-logo.png' />",
|
||||||
"<meta name='twitter:image' content='https://mySaaSapp.com/gptsaastemplate.png' />", // TODO change url and image
|
"<meta name='twitter:image' content='https://opensaas.sh/public-logo.png' />",
|
||||||
"<meta name='twitter:image:width' content='800' />",
|
"<meta name='twitter:image:width' content='800' />",
|
||||||
"<meta name='twitter:image:height' content='400' />",
|
"<meta name='twitter:image:height' content='400' />",
|
||||||
"<meta name='twitter:card' content='summary_large_image' />",
|
"<meta name='twitter:card' content='summary_large_image' />",
|
||||||
@@ -249,6 +249,12 @@ page AdminChartsPage {
|
|||||||
component: import AdminCharts from "@client/admin/pages/Chart"
|
component: import AdminCharts from "@client/admin/pages/Chart"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
route AdminMessagesRoute { path: "/admin/messages", to: AdminMessagesPage }
|
||||||
|
page AdminMessagesPage {
|
||||||
|
authRequired: true,
|
||||||
|
component: import AdminMessages from "@client/admin/pages/Messages"
|
||||||
|
}
|
||||||
|
|
||||||
route AdminFormElementsRoute { path: "/admin/forms/form-elements", to: AdminFormElementsPage }
|
route AdminFormElementsRoute { path: "/admin/forms/form-elements", to: AdminFormElementsPage }
|
||||||
page AdminFormElementsPage {
|
page AdminFormElementsPage {
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
@@ -3,7 +3,7 @@ import AppNavBar from './components/AppNavBar';
|
|||||||
import { useMemo, useEffect, ReactNode } from 'react';
|
import { useMemo, useEffect, ReactNode } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import useAuth from '@wasp/auth/useAuth';
|
import useAuth from '@wasp/auth/useAuth';
|
||||||
import updateCurrentUser from '@wasp/actions/updateCurrentUser'; // TODO fix
|
import updateCurrentUser from '@wasp/actions/updateCurrentUser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* use this component to wrap all child components
|
* use this component to wrap all child components
|
||||||
@@ -25,8 +25,9 @@ export default function App({ children }: { children: ReactNode }) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
const lastSeenAt = new Date(user.lastActiveTimestamp);
|
const lastSeenAt = new Date(user.lastActiveTimestamp);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
if (lastSeenAt.getDate() === today.getDate()) return;
|
if (today.getTime() - lastSeenAt.getTime() > 5 * 60 * 1000) {
|
||||||
updateCurrentUser({ lastActiveTimestamp: today });
|
updateCurrentUser({ lastActiveTimestamp: today });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import DarkModeSwitcher from './DarkModeSwitcher';
|
import DarkModeSwitcher from './DarkModeSwitcher';
|
||||||
import DropdownMessage from './DropdownMessage';
|
import MessageButton from './MessageButton';
|
||||||
import DropdownUser from '../../components/DropdownUser';
|
import DropdownUser from '../../components/DropdownUser';
|
||||||
import type { User } from '@wasp/entities'
|
import type { User } from '@wasp/entities'
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const Header = (props: {
|
|||||||
{/* <!-- Dark Mode Toggler --> */}
|
{/* <!-- Dark Mode Toggler --> */}
|
||||||
|
|
||||||
{/* <!-- Chat Notification Area --> */}
|
{/* <!-- Chat Notification Area --> */}
|
||||||
<DropdownMessage />
|
<MessageButton />
|
||||||
{/* <!-- Chat Notification Area --> */}
|
{/* <!-- Chat Notification Area --> */}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@@ -1,17 +1,15 @@
|
|||||||
// import { useEffect, useRef, useState } from 'react';
|
import { Link } from '@wasp/router';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
const DropdownMessage = () => {
|
|
||||||
|
|
||||||
|
const MessageButton = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="relative" x-data="{ dropdownOpen: false, notifying: true }">
|
<li className="relative" x-data="{ dropdownOpen: false, notifying: true }">
|
||||||
<Link
|
<Link
|
||||||
// TODO: add wasp link
|
|
||||||
className="relative flex h-8.5 w-8.5 items-center justify-center rounded-full border-[0.5px] border-stroke bg-gray hover:text-primary dark:border-strokedark dark:bg-meta-4 dark:text-white"
|
className="relative flex h-8.5 w-8.5 items-center justify-center rounded-full border-[0.5px] border-stroke bg-gray hover:text-primary dark:border-strokedark dark:bg-meta-4 dark:text-white"
|
||||||
to="#"
|
to="/admin/messages"
|
||||||
>
|
>
|
||||||
<span className="absolute -top-0.5 -right-0.5 z-1 h-2 w-2 rounded-full bg-meta-1">
|
<span className="absolute -top-0.5 -right-0.5 z-1 h-2 w-2 rounded-full bg-meta-1">
|
||||||
|
{/* TODO: only animate if there are new messages */}
|
||||||
<span className="absolute -z-1 inline-flex h-full w-full animate-ping rounded-full bg-meta-1 opacity-75"></span>
|
<span className="absolute -z-1 inline-flex h-full w-full animate-ping rounded-full bg-meta-1 opacity-75"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -45,4 +43,4 @@ const DropdownMessage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DropdownMessage;
|
export default MessageButton;
|
10
src/client/admin/pages/Messages.tsx
Normal file
10
src/client/admin/pages/Messages.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// TODO: Add messages page
|
||||||
|
|
||||||
|
function AdminMessages() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>Hello world!</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminMessages
|
Binary file not shown.
Before Width: | Height: | Size: 446 KiB |
BIN
src/client/public/public-logo.png
Normal file
BIN
src/client/public/public-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@@ -86,7 +86,6 @@ export const calculateDailyStats: DailyStatsJob<never, void> = async (_args, con
|
|||||||
const sources = await getSources();
|
const sources = await getSources();
|
||||||
|
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
console.log('source: ', source);
|
|
||||||
let visitors = source.visitors;
|
let visitors = source.visitors;
|
||||||
if (typeof source.visitors !== 'number') {
|
if (typeof source.visitors !== 'number') {
|
||||||
visitors = parseInt(source.visitors);
|
visitors = parseInt(source.visitors);
|
||||||
|
@@ -2,7 +2,6 @@ import { BetaAnalyticsDataClient } from '@google-analytics/data';
|
|||||||
|
|
||||||
const CLIENT_EMAIL = process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL;
|
const CLIENT_EMAIL = process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL;
|
||||||
const PRIVATE_KEY = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY!, 'base64').toString('utf-8');
|
const PRIVATE_KEY = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY!, 'base64').toString('utf-8');
|
||||||
|
|
||||||
const PROPERTY_ID = process.env.GOOGLE_ANALYTICS_PROPERTY_ID;
|
const PROPERTY_ID = process.env.GOOGLE_ANALYTICS_PROPERTY_ID;
|
||||||
|
|
||||||
const analyticsDataClient = new BetaAnalyticsDataClient({
|
const analyticsDataClient = new BetaAnalyticsDataClient({
|
||||||
@@ -22,7 +21,6 @@ export async function getSources() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
// for a list of dimensions and metrics see https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
|
// for a list of dimensions and metrics see https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
|
||||||
// get total page views
|
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
name: 'source',
|
name: 'source',
|
||||||
@@ -37,7 +35,6 @@ export async function getSources() {
|
|||||||
|
|
||||||
let activeUsersPerReferrer: any[] = [];
|
let activeUsersPerReferrer: any[] = [];
|
||||||
if (response?.rows) {
|
if (response?.rows) {
|
||||||
console.log('response.rows (sources): ', response.rows)
|
|
||||||
activeUsersPerReferrer = response.rows.map((row) => {
|
activeUsersPerReferrer = response.rows.map((row) => {
|
||||||
if (row.dimensionValues && row.metricValues) {
|
if (row.dimensionValues && row.metricValues) {
|
||||||
return {
|
return {
|
||||||
|
Reference in New Issue
Block a user