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 = () => ( + + Discord + + +); + +export const TwitterIcon = () => ( + + Twitter + + +); + +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 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.name} +
+

+ + + {project.name} + +

+

+ {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 ( + <> +
+ +
+

Submit a Project

+ +
+
+ +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + http:// + + +
+
+ +
+ +
+ + http:// + + +
+
+ +
+ +
+ + http:// + + +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ + {!!file && ( +

{file} uploaded

+ )} +
+

PNG, JPG, GIF up to 10MB

+
+
+
+
+
+
+ +
+
+ +
+
+
+ + + + ); +}; + +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') + ], +};