Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion opensaas-sh/app_diff/main.wasp.diff
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
httpRoute: (POST, "/payments-webhook")
}
//#endregion
@@ -281,7 +279,6 @@
@@ -291,7 +289,6 @@
component: import AdminCalendar from "@src/admin/elements/calendar/CalendarPage"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- template/app/migrations/20250731133938_drop_upload_url_from_file/migration.sql
+++ opensaas-sh/app/migrations/20250731133938_drop_upload_url_from_file/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `uploadUrl` on the `File` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "File" DROP COLUMN "uploadUrl";
64 changes: 36 additions & 28 deletions opensaas-sh/app_diff/package-lock.json.diff

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion opensaas-sh/app_diff/package.json.diff
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"@aws-sdk/client-s3": "^3.523.0",
"@aws-sdk/s3-presigned-post": "^3.750.0",
@@ -36,6 +41,7 @@
"react-apexcharts": "1.4.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.60.0",
"react-hot-toast": "^2.4.1",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.26.2",
"stripe": "18.1.0",
Expand Down
24 changes: 24 additions & 0 deletions opensaas-sh/app_diff/src/client/App.tsx.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--- template/app/src/client/App.tsx
+++ opensaas-sh/app/src/client/App.tsx
@@ -4,6 +4,7 @@
import './Main.css';
import NavBar from './components/NavBar/NavBar';
import { demoNavigationitems, marketingNavigationItems } from './components/NavBar/constants';
+import { useIsLandingPage } from './hooks/useIsLandingPage';
import CookieConsentBanner from './components/cookie-consent/Banner';
import { Toaster } from '../components/ui/toaster';

@@ -13,11 +14,8 @@
*/
export default function App() {
const location = useLocation();
- const isMarketingPage = useMemo(() => {
- return location.pathname === '/' || location.pathname.startsWith('/pricing');
- }, [location]);
-
- const navigationItems = isMarketingPage ? marketingNavigationItems : demoNavigationitems;
+ const isLandingPage = useIsLandingPage();
+ const navigationItems = isLandingPage ? marketingNavigationItems : demoNavigationitems;

const shouldDisplayAppNavBar = useMemo(() => {
return (
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
--- template/app/src/client/components/NavBar/constants.ts
+++ opensaas-sh/app/src/client/components/NavBar/constants.ts
@@ -9,7 +9,6 @@
@@ -9,12 +9,12 @@

export const marketingNavigationItems: NavigationItem[] = [
{ name: 'Features', to: '/#features' },
- { name: 'Pricing', to: routes.PricingPageRoute.to },
...staticNavigationItems,
] as const;

export const demoNavigationitems: NavigationItem[] = [
{ name: 'AI Scheduler', to: routes.DemoAppRoute.to },
{ name: 'File Upload', to: routes.FileUploadRoute.to },
+ { name: 'Pricing', to: routes.PricingPageRoute.to },
...staticNavigationItems,
] as const;
13 changes: 13 additions & 0 deletions opensaas-sh/app_diff/src/file-upload/FileUploadPage.tsx.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--- template/app/src/file-upload/FileUploadPage.tsx
+++ opensaas-sh/app/src/file-upload/FileUploadPage.tsx
@@ -144,8 +144,8 @@
</h2>
</div>
<p className='mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-muted-foreground'>
- This is an example file upload page using AWS S3. Maybe your app needs this. Maybe it doesn't. But a
- lot of people asked for this feature, so here you go 🤝
+ This is an example file upload page using AWS S3. Maybe your app needs this. Maybe it doesn't. But
+ a lot of people asked for this feature, so here you go 🤝
</p>
<Card className='my-8'>
<CardContent className='space-y-10 my-10 py-8 px-4 mx-auto sm:max-w-lg'>
26 changes: 23 additions & 3 deletions opensaas-sh/app_diff/src/file-upload/fileUploading.ts.diff
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
--- template/app/src/file-upload/fileUploading.ts
+++ opensaas-sh/app/src/file-upload/fileUploading.ts
@@ -1,5 +1,5 @@
-import { createFile } from 'wasp/client/operations';
@@ -1,5 +1,7 @@
+import type { User } from 'wasp/entities';
import axios from 'axios';
+import { createFile } from 'wasp/client/operations';
import { ALLOWED_FILE_TYPES, MAX_FILE_SIZE_BYTES } from './validation';
+import { PrismaClient } from '@prisma/client';

export type FileWithValidType = Omit<File, 'type'> & { type: AllowedFileType };
type AllowedFileType = (typeof ALLOWED_FILE_TYPES)[number];
@@ -63,3 +65,17 @@
function isAllowedFileType(fileType: string): fileType is AllowedFileType {
return (ALLOWED_FILE_TYPES as readonly string[]).includes(fileType);
}
+
+export async function checkIfUserHasReachedFileUploadLimit({ userId, prismaFileDelegate }: { userId: User['id']; prismaFileDelegate: PrismaClient['file'] }) {
+ const numberOfFilesByUser = await prismaFileDelegate.count({
+ where: {
+ user: {
+ id: userId,
+ },
+ },
+ });
+ if (numberOfFilesByUser >= 2) {
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
39 changes: 17 additions & 22 deletions opensaas-sh/app_diff/src/file-upload/operations.ts.diff
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
--- template/app/src/file-upload/operations.ts
+++ opensaas-sh/app/src/file-upload/operations.ts
@@ -1,14 +1,14 @@
@@ -1,6 +1,5 @@
-import * as z from 'zod';
-import { HttpError } from 'wasp/server';
import { type File } from 'wasp/entities';
+import { HttpError } from 'wasp/server';
import {
type CreateFile,
type GetAllFilesByUser,
type GetDownloadFileSignedURL,
@@ -8,6 +7,8 @@
type CreateFileUploadUrl,
type AddFileToDb,
} from 'wasp/server/operations';
+import { checkIfUserHasReachedFileUploadLimit } from './fileUploading';
+import * as z from 'zod';

-import { getUploadFileSignedURLFromS3, getDownloadFileSignedURLFromS3 } from './s3Utils';
import { getUploadFileSignedURLFromS3, getDownloadFileSignedURLFromS3, deleteFileFromS3 } from './s3Utils';
import { ensureArgsSchemaOrThrowHttpError } from '../server/validation';
+import { getDownloadFileSignedURLFromS3, getUploadFileSignedURLFromS3 } from './s3Utils';
import { ALLOWED_FILE_TYPES } from './validation';
@@ -32,6 +33,14 @@
throw new HttpError(401);
}

const createFileInputSchema = z.object({
@@ -37,6 +37,18 @@
userId: context.user.id,
});

+ const numberOfFilesByUser = await context.entities.File.count({
+ where: {
+ user: {
+ id: context.user.id,
+ },
+ },
+ const userFileLimitReached = await checkIfUserHasReachedFileUploadLimit({
+ userId: context.user.id,
+ prismaFileDelegate: context.entities.File,
+ });
+
+ if (numberOfFilesByUser >= 2) {
+ throw new HttpError(403, 'Thanks for trying Open SaaS. This demo only allows 2 file uploads per user.');
+ if (userFileLimitReached) {
+ throw new HttpError(403, 'This demo only allows 2 file uploads per user.');
+ }
+
await context.entities.File.create({
data: {
name: fileName,
const { fileType, fileName } = ensureArgsSchemaOrThrowHttpError(createFileInputSchema, rawArgs);

const { s3UploadUrl, s3UploadFields, key } = await getUploadFileSignedURLFromS3({
15 changes: 0 additions & 15 deletions opensaas-sh/app_diff/src/file-upload/s3Utils.ts.diff

This file was deleted.

16 changes: 4 additions & 12 deletions opensaas-sh/app_diff/src/landing-page/contentSections.tsx.diff
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- template/app/src/landing-page/contentSections.tsx
+++ opensaas-sh/app/src/landing-page/contentSections.tsx
@@ -0,0 +1,247 @@
@@ -0,0 +1,239 @@
+import { routes } from 'wasp/client/router';
+import type { NavigationItem } from '../client/components/NavBar/NavBar';
+import blog from '../client/static/assets/blog.webp';
Expand Down Expand Up @@ -113,20 +113,12 @@
+ 'I used Wasp to build and sell my AI-augmented SaaS app for marketplace vendors within two months!',
+ },
+ {
+ name: 'Jonathan Cocharan',
+ role: 'Entrepreneur',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1910056203863883776/jtfVWaEG_400x400.jpg',
+ socialUrl: 'https://twitter.com/JonathanCochran',
+ 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 🤯!',
+ },
+ {
+ name: 'Billy Howell',
+ role: 'Entrepreneur',
+ role: 'Founder @ Stupid Simple Apps',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1877734205561430016/jjpG4mS6_400x400.jpg',
+ socialUrl: 'https://twitter.com/billyjhowell',
+ quote:
+ "Congrats! I am loving Wasp. It's really helped me, a self-taught coder increase my confidence. I feel like I've finally found the perfect, versatile stack for all my projects instead of trying out a new one each time.",
+ "Congrats! I am loving Wasp & Open SaaS. It's really helped me, a self-taught coder increase my confidence. I feel like I've finally found the perfect, versatile stack for all my projects instead of trying out a new one each time.",
+ },
+ {
+ name: 'Tim Skaggs',
Expand All @@ -140,7 +132,7 @@
+ role: 'Founder @ Microinfluencer.club',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1927721707164377089/it8oCAkf_400x400.jpg',
+ socialUrl: 'https://twitter.com/CamBlackwood95',
+ quote: 'Setting up a full stack SaaS in 1 minute with WaspLang.',
+ quote: 'Setting up a full stack SaaS in 1 minute with Wasp.',
+ },
+ {
+ name: 'JLegendz',
Expand Down
4 changes: 2 additions & 2 deletions opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ The template comes with:
We've also created a bunch of LLM-friendly documentation:
- [Open SaaS Docs - LLMs.txt](https://docs.opensaas.sh/llms.txt) - Links to the raw text docs.
- **[Open SaaS Docs - LLMs-full.txt](https://docs.opensaas.sh/llms-full.txt) - Complete docs as one text file.** ✅😎
- Coming Soon! ~~[Wasp Docs - LLMs.txt](https://wasp.sh/llms.txt)~~ - Links to the raw text docs.
- Coming Soon! ~~[Wasp Docs - LLMs-full.txt](https://wasp.sh/llms-full.txt)~~ - Complete docs as one text file.
- [Wasp Docs - LLMs.txt](https://wasp.sh/llms.txt) - Links to the raw text docs.
- **[Wasp Docs - LLMs-full.txt](https://wasp.sh/llms-full.txt) - Complete docs as one text file.**

Add these to your AI-assisted IDE settings so you can easily reference them in your chat sessions with the LLM.
**In most cases, you'll want to pass the `llms-full.txt` to the LLM and ask it to help you with a specific task.**
Expand Down
6 changes: 4 additions & 2 deletions template/app/.cursor/rules/authentication.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,18 @@ See the Wasp Auth docs for available methods and complete guides [wasp-overview.
- Redirect or show alternative content if the user is not authenticated.
```typescript
import { useAuth } from 'wasp/client/auth';
import { Redirect } from 'wasp/client/router'; // Or use Link
import { useNavigate } from 'react-router-dom';

const MyProtectedPage = () => {
const { data: user, isLoading, error } = useAuth(); // Returns AuthUser | null
const navigate = useNavigate();

if (isLoading) return <div>Loading...</div>;
// If error, it likely means the auth session is invalid/expired
if (error || !user) {
// Redirect to login page defined in main.wasp (auth.onAuthFailedRedirectTo)
// Or return <Redirect to="/login" />;
// or use the navigate hook from react-router-dom for more fine-grained control
navigate('/some-other-path');
return <div>Please log in to access this page.</div>;
}

Expand Down
14 changes: 12 additions & 2 deletions template/app/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,13 @@ page FileUploadPage {
component: import FileUpload from "@src/file-upload/FileUploadPage"
}

action createFile {
fn: import { createFile } from "@src/file-upload/operations",
action createFileUploadUrl {
fn: import { createFileUploadUrl } from "@src/file-upload/operations",
entities: [User, File]
}

action addFileToDb {
fn: import { addFileToDb } from "@src/file-upload/operations",
entities: [User, File]
}

Expand All @@ -235,6 +240,11 @@ query getDownloadFileSignedURL {
fn: import { getDownloadFileSignedURL } from "@src/file-upload/operations",
entities: [User, File]
}

action deleteFile {
fn: import { deleteFile } from "@src/file-upload/operations",
entities: [User, File]
}
//#endregion

//#region Analytics
Expand Down
Loading