Skip to content

Refactor file upload and toast notifications #472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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