All in dirs (#154)

* Split the project into template and opensaas-sh (demo app (diff) + docs).

* fix
This commit is contained in:
Martin Šošić
2024-06-04 13:24:32 +02:00
committed by GitHub
parent 496480509a
commit 04553cd60c
265 changed files with 22081 additions and 70 deletions

View File

@@ -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<string | undefined>(undefined);
const [isAdminFilter, setIsAdminFilter] = useState<boolean | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatusOptions[]>([]);
+ 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 (
<div className='flex flex-col gap-4'>
+ {/* Floating Demo Announcement */}
+ {isDemoInfoVisible && (
+ <div className='fixed z-999 bottom-0 mb-2 left-1/2 -translate-x-1/2 lg:mb-4 bg-gray-700 rounded-full px-3.5 py-2 text-sm text-white duration-300 ease-in-out hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600'>
+ <div className='px-4 flex flex-row gap-2 items-center my-1'>
+ <span className='text-gray-100'>
+ You are viewing mock user data only ;)
+ </span>
+ <button className=' pl-2.5 text-gray-400 text-xl font-bold' onClick={() => handleDemoInfoClose()}>
+ X
+ </button>
+ </div>
+ </div>
+ )}
<div className='rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark'>
<div className='flex-col flex items-start justify-between p-6 gap-3 w-full bg-gray-100/40 dark:bg-gray-700/50'>
<span className='text-sm font-medium'>Filters:</span>

View File

@@ -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 (
<DefaultLayout>
+ {/* Floating Demo Announcement */}
+ {isDemoInfoVisible && (
+ <div className='fixed z-999 bottom-0 mb-2 left-1/2 -translate-x-1/2 lg:mb-4 bg-gray-700 rounded-full px-3.5 py-2 text-sm text-white duration-300 ease-in-out hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600'>
+ <div className='px-4 flex flex-row gap-2 items-center my-1'>
+ <span className='text-gray-100 text-center'>This is actual data from Stripe test purchases. <br/> Try out purchasing a <Link to='/pricing' className="underline text-yellow-400">test product</Link>!</span>
+ <button className=' pl-2.5 text-gray-400 text-xl font-bold' onClick={() => handleDemoInfoClose()}>
+ X
+ </button>
+ </div>
+ </div>
+ )}
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
<TotalPageViewsCard
totalPageViews={stats?.dailyStats.totalViews}

View File

@@ -0,0 +1,60 @@
--- template/app/src/client/landing-page/Announcement.tsx
+++ opensaas-sh/app/src/client/landing-page/Announcement.tsx
@@ -0,0 +1,57 @@
+import { useState, useEffect } from 'react';
+import { AiFillGithub } from 'react-icons/ai';
+
+type TimeLeft = { hours: string; minutes: string; seconds: string };
+
+export default function Announcement() {
+ const [timeLeft, setTimeLeft] = useState<TimeLeft | undefined>(
+ 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 (
+ <div className='flex items-center justify-center gap-3 border-b border-gray-300 border-dashed text-center py-6 text-sm'>
+ Open SaaS trending on{' '}
+ <a
+ href='https://github.com/trending#:~:text=wasp%2Dlang%20/%20open%2Dsaas'
+ target='_blank'
+ rel='noopener noreferrer'
+ className='flex items-center justify-center gap-2 bg-purple-200 hover:bg-purple-300 text-gray-900 border-b border-1 border-purple-300 hover:border-purple-400 py-1 px-3 -my-1 rounded-full shadow-lg hover:shadow-md duration-200 ease-in-out tracking-wider'
+ >
+ <span>GitHub</span>
+ <AiFillGithub />
+ </a>
+ 📈
+ </div>
+ );
+}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,189 @@
--- template/app/src/client/landing-page/contentSections.ts
+++ opensaas-sh/app/src/client/landing-page/contentSections.ts
@@ -1,74 +1,150 @@
-import { DOCS_URL, BLOG_URL } from '../../shared/constants';
-import daBoiAvatar from '../static/da-boi.png';
-import avatarPlaceholder from '../static/avatar-placeholder.png';
import { routes } from 'wasp/client/router';
+import { DOCS_URL, BLOG_URL, GITHUB_URL } from '../../shared/constants';
+import daBoiAavatar from '../static/da-boi.png';
export const navigation = [
{ name: 'Features', href: '#features' },
- { name: 'Pricing', href: routes.PricingPageRoute.build() },
{ name: 'Documentation', href: DOCS_URL },
{ name: 'Blog', href: BLOG_URL },
];
export const features = [
{
- name: 'Cool Feature #1',
- description: 'Describe your cool feature here.',
+ name: 'Open-Source Philosophy',
+ description:
+ 'The repo and framework are 100% open-source, and so are the services wherever possible. Still missing something? Contribute!',
icon: '🤝',
href: DOCS_URL,
},
{
- name: 'Cool Feature #2',
- description: 'Describe your cool feature here.',
+ name: 'DIY Auth, Done For You',
+ description: 'Pre-configured full-stack Auth that you own. No 3rd-party services or hidden fees.',
icon: '🔐',
- href: DOCS_URL,
+ href: DOCS_URL + '/guides/authentication/',
},
{
- name: 'Cool Feature #3',
- description: 'Describe your cool feature here.',
+ name: 'Full-stack Type Safety',
+ description:
+ 'Full support for TypeScript with auto-generated types that span the whole stack. Nothing to configure!',
icon: '🥞',
href: DOCS_URL,
},
{
- name: 'Cool Feature #4',
- description: 'Describe your cool feature here.',
+ name: 'Stripe Integration',
+ description:
+ "No SaaS is complete without payments. That's why payments and the necessary webhooks are built-in.",
icon: '💸',
+ href: DOCS_URL + '/guides/stripe-integration/',
+ },
+ {
+ name: 'Admin Dashboard',
+ description: 'Graphs! Tables! Analytics w/ Plausible or Google! All in one place. Ooooooooooh.',
+ icon: '📈',
+ href: DOCS_URL + '/general/admin-dashboard/',
+ },
+ {
+ name: 'Email Sending',
+ description:
+ 'Email sending built-in. Combine it with the cron jobs feature to easily send emails to your customers.',
+ icon: '📧',
+ href: DOCS_URL + '/guides/email-sending/',
+ },
+ {
+ name: 'OpenAI API Implemented',
+ description: 'Have a sweet AI-powered app concept? Get your idea shipped to potential customers in days!',
+ icon: '🤖',
+ href: DOCS_URL,
+ },
+ {
+ name: 'File Uploads with AWS',
+ description: 'File upload examples with AWS S3 presigned URLs are included and fully documented!',
+ icon: '📁',
+ href: DOCS_URL + '/guides/file-uploading/',
+ },
+ {
+ name: 'Deploy Anywhere. Easily.',
+ description:
+ 'No vendor lock-in because you own all your code. Deploy yourself, or let Wasp deploy it for you with a single command.',
+ icon: '🚀 ',
+ href: DOCS_URL + '/guides/deploying/',
+ },
+ {
+ name: 'Blog w/ Astro',
+ description:
+ 'Built-in blog with the Astro framework. Write your posts in Markdown, and watch your SEO performance take off.',
+ icon: '📝',
+ href: DOCS_URL + '/start/guided-tour/',
+ },
+ {
+ name: 'Complete Documentation & Support',
+ description: "We don't leave you hanging. We have detailed docs and a Discord community to help!",
+ icon: '🫂',
href: DOCS_URL,
},
];
export const testimonials = [
- {
- name: 'Da Boi',
- role: 'Wasp Mascot',
- avatarSrc: daBoiAvatar,
- socialUrl: 'https://twitter.com/wasplang',
- quote: "I don't even know how to code. I'm just a plushie.",
- },
- {
- name: 'Mr. Foobar',
- role: 'Founder @ Cool Startup',
- avatarSrc: avatarPlaceholder,
- socialUrl: '',
- quote: 'This product makes me cooler than I already am.',
- },
- {
- name: 'Jamie',
- role: 'Happy Customer',
- avatarSrc: avatarPlaceholder,
- socialUrl: '#',
- quote: 'My cats love it!',
+ // {
+ // name: 'Jason Warner',
+ // role: 'former CTO @ GitHub',
+ // avatarSrc: 'https://pbs.twimg.com/profile_images/1538765024021258240/qXJBzw6U_400x400.jpg',
+ // socialUrl: 'https://twitter.com/jasoncwarner',
+ // quote:
+ // "I've actually had a bunch of fun with [Wasp]... I loved Batman.js back in the day and getting some of those vibes.",
+ // },
+ {
+ name: 'Max Khamrovskyi',
+ role: 'Senior Eng @ Red Hat',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1719397191205179392/V_QrGPSO_400x400.jpg',
+ socialUrl: 'https://twitter.com/maksim36ua',
+ quote: 'I used Wasp to build and sell my AI-augmented SaaS app for marketplace vendors within two months!',
+ },
+ // {
+ // name: 'Da Boi',
+ // role: 'Wasp Mascot',
+ // avatarSrc: daBoiAavatar,
+ // socialUrl: 'https://twitter.com/wasplang',
+ // quote: "I don't even know how to code. I'm just a plushie.",
+ // },
+ {
+ name: 'Tim Skaggs',
+ role: 'Founder @ Antler US',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1486119226771480577/VptdEo6A_400x400.png',
+ socialUrl: 'https://twitter.com/tskaggs',
+ quote: 'Nearly done with a MVP in 3 days of part-time work... and deployed on Fly.io in 10 minutes.',
+ },
+ // {
+ // name: 'Fecony',
+ // role: 'Wasp Expert',
+ // avatarSrc: 'https://pbs.twimg.com/profile_images/1560677466749943810/QIFuQMqU_400x400.jpg',
+ // socialUrl: 'https://twitter.com/webrickony',
+ // quote: 'My cats love it!',
+ // },
+ {
+ name: 'Jonathan Cocharan',
+ role: 'Entrepreneur',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/926142421653753857/o6Hmcbr7_400x400.jpg',
+ socialUrl: 'https://twitter.com/jonathancocharan',
+ quote:
+ 'In just 6 nights... my SaaS app is live 🎉! Huge thanks to the amazing @wasplang community 🙌 for their guidance along the way. These tools are incredibly efficient 🤯!',
},
];
export const faqs = [
{
id: 1,
- question: 'Whats the meaning of life?',
- answer: '42.',
- href: 'https://en.wikipedia.org/wiki/42_(number)',
+ question: 'Why is this SaaS Template free and open-source?',
+ answer:
+ 'We believe the best product is made when the community puts their heads together. We also believe a quality starting point for a web app should be free and available to everyone. Our hope is that together we can create the best SaaS template out there and bring our ideas to customers quickly.',
+ },
+ {
+ id: 2,
+ question: "What's Wasp?",
+ href: 'https://wasp-lang.dev',
+ answer: "It's the fastest way to develop full-stack React + NodeJS + Prisma apps and it's what gives this template superpowers. Wasp relies on React, NodeJS, and Prisma to define web components and server queries and actions. Wasp's secret sauce is its compiler which takes the client, server code, and config file and outputs the client app, server app and deployment code, supercharging the development experience. Combined with this template, you can build a SaaS app in record time.",
},
];
export const footerNavigation = {
app: [
+ { name: 'Github', href: GITHUB_URL },
{ name: 'Documentation', href: DOCS_URL },
{ name: 'Blog', href: BLOG_URL },
],