This commit is contained in:
vincanger 2023-12-01 14:35:17 +01:00
parent 41bcd70496
commit 8d5ab0d2ee
16 changed files with 74 additions and 41 deletions

2
.gitignore vendored
View File

@ -6,6 +6,6 @@ fly config/fly-client.toml
fly config/fly-server.toml
fly-client.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
saastemplate-381911-6dc3caae2204.json

View File

@ -7,10 +7,7 @@ export default defineConfig({
integrations: [
starlightBlog({
title: 'The Best Blog Ever',
sidebar: [
{
}
],
sidebar: [{}],
authors: {
vince: {
name: 'Vince',
@ -22,10 +19,8 @@ export default defineConfig({
}),
starlight({
title: 'Open SaaS Docs',
// root: '/start/introdcution/',
// site: 'https://www.my-site.dev', // TODO: Change this to your site
editLink: {
baseUrl: 'https://github.com/withastro/starlight/edit/main/', // TODO: change
baseUrl: 'https://github.com/wasp-lang/open-saas/edit/main',
},
components: {
MarkdownContent: 'starlight-blog/overrides/MarkdownContent.astro',
@ -33,7 +28,7 @@ export default defineConfig({
ThemeSelect: 'starlight-blog/overrides/ThemeSelect.astro',
},
social: {
github: 'https://github.com/wasp-lang',
github: 'https://github.com/wasp-lang/open-saas',
twitter: 'https://twitter.com/wasp_lang',
discord: 'https://discord.gg/aCamt5wCpS',
},

View File

@ -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.
<!-- TODO: add repo links -->
:::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

View File

@ -127,7 +127,7 @@ entity User {=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.

View File

@ -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
<!-- 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...*
*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.
```js
```js {7}
app SaaSTemplate {
wasp: {
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 accounts 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.
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
echo -n "PRIVATE_KEY" | base64
```

View File

@ -14,9 +14,8 @@ This template is:
2. completely free to use and distribute
3. comes with a ton of features out of the box!
<!-- // TODO: add links -->
Try it out here:
Check Out the Code:
Try it out here: [OpenSaaS.sh](https://opensaas.sh)
Check Out the Code: Open SaaS [GitHub Repo](https://github.com/wasp-lang/open-saas)
## 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"]
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]()!
<!-- {/* TODO: add link */} -->
We could always use some help tying up loose ends, so consider [contributing](https://github.com/wasp-lang/open-saas/blob/main/CONTRIBUTING.md)!
:::
## Getting acquainted with the codebase

View File

@ -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:
```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
cd // TODO ADD REPO NAME
cd open-saas
```
### Start your DB

View File

@ -5,10 +5,10 @@ app SaaSTemplate {
title: "My Open SaaS App",
head: [
"<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:image' content='src/client/static/image.png' />",
"<meta name='twitter:image' content='https://mySaaSapp.com/gptsaastemplate.png' />", // TODO change url and image
"<meta property='og:image' content='https://opensaas.sh/public-logo.png' />",
"<meta name='twitter:image' content='https://opensaas.sh/public-logo.png' />",
"<meta name='twitter:image:width' content='800' />",
"<meta name='twitter:image:height' content='400' />",
"<meta name='twitter:card' content='summary_large_image' />",
@ -249,6 +249,12 @@ page AdminChartsPage {
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 }
page AdminFormElementsPage {
authRequired: true,

View File

@ -3,7 +3,7 @@ import AppNavBar from './components/AppNavBar';
import { useMemo, useEffect, ReactNode } from 'react';
import { useLocation } from 'react-router-dom';
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
@ -25,8 +25,9 @@ export default function App({ children }: { children: ReactNode }) {
if (user) {
const lastSeenAt = new Date(user.lastActiveTimestamp);
const today = new Date();
if (lastSeenAt.getDate() === today.getDate()) return;
updateCurrentUser({ lastActiveTimestamp: today });
if (today.getTime() - lastSeenAt.getTime() > 5 * 60 * 1000) {
updateCurrentUser({ lastActiveTimestamp: today });
}
}
}, [user]);

View File

@ -1,5 +1,5 @@
import DarkModeSwitcher from './DarkModeSwitcher';
import DropdownMessage from './DropdownMessage';
import MessageButton from './MessageButton';
import DropdownUser from '../../components/DropdownUser';
import type { User } from '@wasp/entities'
@ -67,7 +67,7 @@ const Header = (props: {
{/* <!-- Dark Mode Toggler --> */}
{/* <!-- Chat Notification Area --> */}
<DropdownMessage />
<MessageButton />
{/* <!-- Chat Notification Area --> */}
</ul>

View File

@ -1,17 +1,15 @@
// import { useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
const DropdownMessage = () => {
import { Link } from '@wasp/router';
const MessageButton = () => {
return (
<li className="relative" x-data="{ dropdownOpen: false, notifying: true }">
<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"
to="#"
to="/admin/messages"
>
<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>
@ -45,4 +43,4 @@ const DropdownMessage = () => {
);
};
export default DropdownMessage;
export default MessageButton;

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -86,7 +86,6 @@ export const calculateDailyStats: DailyStatsJob<never, void> = async (_args, con
const sources = await getSources();
for (const source of sources) {
console.log('source: ', source);
let visitors = source.visitors;
if (typeof source.visitors !== 'number') {
visitors = parseInt(source.visitors);

View File

@ -2,7 +2,6 @@ import { BetaAnalyticsDataClient } from '@google-analytics/data';
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 PROPERTY_ID = process.env.GOOGLE_ANALYTICS_PROPERTY_ID;
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
// get total page views
dimensions: [
{
name: 'source',
@ -37,7 +35,6 @@ export async function getSources() {
let activeUsersPerReferrer: any[] = [];
if (response?.rows) {
console.log('response.rows (sources): ', response.rows)
activeUsersPerReferrer = response.rows.map((row) => {
if (row.dimensionValues && row.metricValues) {
return {