diff --git a/examples/hackathon/.gitignore b/examples/hackathon/.gitignore
new file mode 100644
index 000000000..0078ed5b5
--- /dev/null
+++ b/examples/hackathon/.gitignore
@@ -0,0 +1,6 @@
+/.wasp/
+/.env.server
+/.env.client
+
+# Local Netlify folder
+.netlify
diff --git a/examples/hackathon/.wasproot b/examples/hackathon/.wasproot
new file mode 100644
index 000000000..ca2cfdb48
--- /dev/null
+++ b/examples/hackathon/.wasproot
@@ -0,0 +1 @@
+File marking the root of Wasp project.
diff --git a/examples/hackathon/README.md b/examples/hackathon/README.md
new file mode 100644
index 000000000..387ea7a72
--- /dev/null
+++ b/examples/hackathon/README.md
@@ -0,0 +1,11 @@
+
+
+
+
+# 🐝🚀 Wasp Hackathon Submission App
+
+This app was created for Wasp's Beta Hackathon (aka Betathon), so that participants would have a central place to learn about the hackathon and submit their projects. The app was deployed to Railway.app and is live [here](https://betathon-production.up.railway.app/).
+
+Visit the homepage to get started with [Wasp](https://wasp-lang.dev).
+
+Feel free to use this as a template for your own Hackathons!
\ No newline at end of file
diff --git a/examples/hackathon/main.wasp b/examples/hackathon/main.wasp
new file mode 100644
index 000000000..4febaf476
--- /dev/null
+++ b/examples/hackathon/main.wasp
@@ -0,0 +1,40 @@
+app hackathonBetaSubmissions {
+ wasp: {
+ version: "^0.7.0"
+ },
+ db: {
+ system: PostgreSQL
+ },
+ title: "hackathon-beta-submissions",
+ dependencies: [
+ ("react-feather", "2.0.10"),
+ ]
+}
+
+entity Submission {=psl
+ name String @id @unique
+ email String @unique
+ github String
+ description String
+ twitter String?
+ country String?
+ website String?
+ image String?
+ approved Boolean @default(false)
+ createdAt DateTime @default(now())
+psl=}
+
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
+ component: import Main from "@client/MainPage"
+}
+
+action submitProject {
+ fn: import { submitProject } from "@server/actions.js",
+ entities: [Submission]
+}
+
+query getProjects {
+ fn: import { getProjects } from "@server/queries.js",
+ entities: [Submission]
+}
\ No newline at end of file
diff --git a/examples/hackathon/postcss.config.js b/examples/hackathon/postcss.config.js
new file mode 100644
index 000000000..2e82a460b
--- /dev/null
+++ b/examples/hackathon/postcss.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {
+ config: './tailwind.config.js',
+ },
+ autoprefixer: {},
+ },
+};
diff --git a/examples/hackathon/src/.waspignore b/examples/hackathon/src/.waspignore
new file mode 100644
index 000000000..1c432f30d
--- /dev/null
+++ b/examples/hackathon/src/.waspignore
@@ -0,0 +1,3 @@
+# Ignore editor tmp files
+**/*~
+**/#*#
diff --git a/examples/hackathon/src/client/Main.css b/examples/hackathon/src/client/Main.css
new file mode 100644
index 000000000..507258902
--- /dev/null
+++ b/examples/hackathon/src/client/Main.css
@@ -0,0 +1,117 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+* {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ box-sizing: border-box;
+
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ font-family: Inter;
+ background-color: #f5f5f5;
+}
+
+main {
+ padding: 4rem 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+main p {
+ font-size: 1.2rem;
+}
+
+.logo {
+ margin-bottom: 2rem;
+}
+
+.logo img {
+ max-height: 200px;
+}
+
+.welcome-title {
+ font-weight: 500;
+}
+
+.welcome-subtitle {
+ font-weight: 400;
+ margin-bottom: 3rem;
+}
+
+.buttons {
+ display: flex;
+ flex-direction: row;
+}
+
+.buttons .button:not(:last-child) {
+ margin-right: 0.5rem;
+}
+
+.button {
+ border-radius: 3px;
+ font-size: 1.2rem;
+ padding: 1rem 2rem;
+ text-align: center;
+ font-weight: 700;
+ text-decoration: none;
+}
+
+.button-filled {
+ border: 2px solid #bf9900;
+ background-color: #bf9900;
+ color: #f4f4f4;
+}
+
+.button-outline {
+ border: 2px solid #8a9cff;
+ color: #8a9cff;
+ background-color: none;
+}
+
+code {
+ border-radius: 5px;
+ padding: 0.2rem;
+ background: #efefef;
+ font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
+ Bitstream Vera Sans Mono, Courier New, monospace;
+}
+
+.submit {
+ font-size: 1rem;
+ /* background-color: #f4e8b3; */
+ /* color: #eab308; */
+ font-weight: 600;
+ /* background-image:
+ linear-gradient(90deg, #bf9900 30%, #121212 100%);
+ background-clip: text;
+ color: transparent; */
+}
+
+.submit:hover {
+ background-color: #fcf3d0;
+
+}
+
+.gradient-text {
+ background-image:
+ linear-gradient(90deg, #bf9900 30%, #646464 100%);
+ background-clip: text;
+ color: transparent;
+}
+
+:focus {
+ outline: 1px solid #bf9900;
+}
\ No newline at end of file
diff --git a/examples/hackathon/src/client/MainPage.tsx b/examples/hackathon/src/client/MainPage.tsx
new file mode 100644
index 000000000..a7761ad91
--- /dev/null
+++ b/examples/hackathon/src/client/MainPage.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import betathonLogo from './betathonLogo.png';
+import './Main.css';
+import Nav from './components/Navbar';
+import Form from './components/SubmissionForm';
+import Projects from './components/Projects';
+
+const MainPage = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+export default MainPage;
diff --git a/examples/hackathon/src/client/betathonLogo.png b/examples/hackathon/src/client/betathonLogo.png
new file mode 100644
index 000000000..f840f5b0d
Binary files /dev/null and b/examples/hackathon/src/client/betathonLogo.png differ
diff --git a/examples/hackathon/src/client/components/Navbar.tsx b/examples/hackathon/src/client/components/Navbar.tsx
new file mode 100644
index 000000000..1f83467fb
--- /dev/null
+++ b/examples/hackathon/src/client/components/Navbar.tsx
@@ -0,0 +1,177 @@
+import React from 'react';
+import waspLogo from '../waspLogo.png';
+import { Star } from 'react-feather';
+
+export const DiscordIcon = () => (
+
+);
+
+export const TwitterIcon = () => (
+
+);
+
+const Navbar = () => {
+
+ function scrollToTargetAdjusted() {
+ var element = document.getElementById('submission');
+ var headerOffset = 75;
+ var elementPosition = element.getBoundingClientRect().top;
+ var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
+
+ window.scrollTo({
+ top: offsetPosition,
+ behavior: 'smooth',
+ });
+ }
+
+ const Logo = () => (
+
+
+
+
+
+ Wasp βetathon
+
+
βetathon
+
+ );
+
+ const SocialIcon = ({ Icon, url }) => (
+
+
+
+ );
+
+ const GitHubButton = () => (
+
+
+
+
+ Star us on GitHub
+
+ );
+
+
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default Navbar;
diff --git a/examples/hackathon/src/client/components/Projects.tsx b/examples/hackathon/src/client/components/Projects.tsx
new file mode 100644
index 000000000..607f054bd
--- /dev/null
+++ b/examples/hackathon/src/client/components/Projects.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import { useQuery } from '@wasp/queries';
+import getProjects from '@wasp/queries/getProjects';
+import betathonLogo from '../betathonLogo.png';
+
+const Projects = () => {
+ const { data: projects, status } = useQuery(getProjects);
+
+ return (
+ <>
+
+
+
+
+ Submitted Projects{' '}
+
+
+ {status === 'success' && projects.length ? (
+ projects.map((project) => (
+
+
+
+
+
+
+ {project.description.length > 120
+ ? project.description.substring(0, 142).concat('...')
+ : project.description}
+
+
+ ))
+ ) : (
+
+
Nothing to see here... yet
+
+ Looking for some inspiration?
+ Check out our{' '}
+
+ {' '}
+ Example Wasp Apps
+ {' '}
+
+ BTW, This is also a Wasp App! 🧙♂️
+
+
+
+ )}
+
+
+
+
+ >
+ );
+};
+export default Projects;
diff --git a/examples/hackathon/src/client/components/SubmissionForm.tsx b/examples/hackathon/src/client/components/SubmissionForm.tsx
new file mode 100644
index 000000000..9e65e8c47
--- /dev/null
+++ b/examples/hackathon/src/client/components/SubmissionForm.tsx
@@ -0,0 +1,285 @@
+import React, { useState } from 'react';
+import submitProject from '@wasp/actions/submitProject';
+
+export type Submission = {
+ name: string;
+ email: string;
+ country: string;
+ website: string;
+ github: string;
+ twitter: string;
+ description: string;
+ image: string;
+};
+
+const SubmissionForm = () => {
+ const [file, setFile] = useState();
+ const [isUploading, setIsUploading] = useState(false);
+ const [imageLink, setImageLink] = useState('');
+
+ const onFileUpload = async (event) => {
+ setIsUploading(true);
+ const clientId = 'd4ecb4220cf055b'
+ const auth = 'Client-ID ' + clientId;
+
+ const formData = new FormData();
+ formData.append('image', event.target?.files[0]);
+
+ try {
+ const imgur = await fetch('https://api.imgur.com/3/upload', {
+ method: 'POST',
+ body: formData,
+ headers: {
+ Authorization: auth,
+ Accept: 'application/json',
+ },
+ });
+
+ const json = await imgur.json();
+
+ if (!json.success) {
+ throw new Error('Image upload failed');
+ }
+ setFile(event.target.files[0].name);
+ setImageLink(json.data.link);
+ } catch (error) {
+ console.error('error uploading image');
+ }
+ setIsUploading(false);
+ };
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ const data = new FormData(event.target);
+ const value = Object.fromEntries(data.entries());
+ delete value['file-upload'];
+ value.image = imageLink;
+
+ try {
+ await submitProject(value as Submission);
+ alert('Project submitted successfully! It will be visible once it is approved.');
+ event.target.reset();
+ } catch (e) {
+ console.error('Error while submitting project', e);
+ alert('Error while submitting project');
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+export default SubmissionForm;
diff --git a/examples/hackathon/src/client/react-app-env.d.ts b/examples/hackathon/src/client/react-app-env.d.ts
new file mode 100644
index 000000000..e80934ce3
--- /dev/null
+++ b/examples/hackathon/src/client/react-app-env.d.ts
@@ -0,0 +1,60 @@
+declare module '*.avif' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.bmp' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.gif' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpeg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.webp' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.svg' {
+ import * as React from 'react';
+
+ export const ReactComponent: React.FunctionComponent & { title?: string }>;
+
+ const src: string;
+ export default src;
+}
+
+declare module '*.module.css' {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+
+declare module '*.module.scss' {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+
+declare module '*.module.sass' {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
diff --git a/examples/hackathon/src/client/tsconfig.json b/examples/hackathon/src/client/tsconfig.json
new file mode 100644
index 000000000..34c23b30c
--- /dev/null
+++ b/examples/hackathon/src/client/tsconfig.json
@@ -0,0 +1,39 @@
+{
+ "compilerOptions": {
+ // JSX support
+ "jsx": "preserve",
+ // Enable default imports in TypeScript.
+ "esModuleInterop": true,
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ // The following settings enable IDE support in user-provided source files.
+ // Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/web-app/",
+ "paths": {
+ // Resolve all "@wasp" imports to the generated source code.
+ "@wasp/*": [
+ "src/*"
+ ],
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": ["../../.wasp/out/web-app/node_modules/@types"]
+ }
+}
diff --git a/examples/hackathon/src/client/waspLogo.png b/examples/hackathon/src/client/waspLogo.png
new file mode 100644
index 000000000..dcef40113
Binary files /dev/null and b/examples/hackathon/src/client/waspLogo.png differ
diff --git a/examples/hackathon/src/server/actions.js b/examples/hackathon/src/server/actions.js
new file mode 100644
index 000000000..a39c5ef5d
--- /dev/null
+++ b/examples/hackathon/src/server/actions.js
@@ -0,0 +1,8 @@
+export const submitProject = async (project, context) => {
+
+ const newProject = context.entities.Submission.create({
+ data: project,
+ });
+
+ return newProject;
+};
diff --git a/examples/hackathon/src/server/queries.js b/examples/hackathon/src/server/queries.js
new file mode 100644
index 000000000..6677a6fbf
--- /dev/null
+++ b/examples/hackathon/src/server/queries.js
@@ -0,0 +1,11 @@
+export const getProjects = async (args, context) => {
+
+ return context.entities.Submission.findMany({
+ where: {
+ approved: true,
+ },
+ orderBy: {
+ createdAt: 'desc',
+ },
+ });
+};
diff --git a/examples/hackathon/src/server/tsconfig.json b/examples/hackathon/src/server/tsconfig.json
new file mode 100644
index 000000000..8d313c451
--- /dev/null
+++ b/examples/hackathon/src/server/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ // Enable default imports in TypeScript.
+ "esModuleInterop": true,
+ "allowJs": true,
+ // The following settings enable IDE support in user-provided source files.
+ // Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/server/",
+ "paths": {
+ // Resolve all "@wasp" imports to the generated source code.
+ "@wasp/*": [
+ "src/*"
+ ],
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": ["../../.wasp/out/server/node_modules/@types"]
+ }
+}
diff --git a/examples/hackathon/src/shared/tsconfig.json b/examples/hackathon/src/shared/tsconfig.json
new file mode 100644
index 000000000..20fcac843
--- /dev/null
+++ b/examples/hackathon/src/shared/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ // Enable default imports in TypeScript.
+ "esModuleInterop": true,
+ "allowJs": true,
+ // The following settings enable IDE support in user-provided source files.
+ // Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/server/",
+ "paths": {
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": ["../../.wasp/out/server/node_modules/@types"]
+ }
+}
diff --git a/examples/hackathon/tailwind.config.js b/examples/hackathon/tailwind.config.js
new file mode 100644
index 000000000..d099a521f
--- /dev/null
+++ b/examples/hackathon/tailwind.config.js
@@ -0,0 +1,19 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ['./src/**/*.{js,ts,jsx,tsx}'],
+ theme: {
+ extend: {
+ skew: {
+ 'min2': '-2deg',
+ 'min4': '-4deg',
+ 'min6': '-6deg',
+ }
+ },
+ focus: {
+ outline: 'none',
+ },
+ },
+ plugins: [
+ // require('@tailwindcss/aspect-ratio')
+ ],
+};