2
.gitignore
vendored
@ -4,6 +4,8 @@
|
||||
|
||||
# editor related
|
||||
.idea/
|
||||
*.swo
|
||||
*.swp
|
||||
|
||||
# macOS related
|
||||
.DS_Store
|
||||
|
@ -1,14 +1,8 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const getExcuse = async () => {
|
||||
return axios
|
||||
.get('https://api.devexcus.es/')
|
||||
.then(res => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
const response = await axios.get('https://api.devexcus.es/')
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getAllSavedExcuses = async (_args, context) => {
|
||||
|
3
project-page/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
36
project-page/.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
34
project-page/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
102
project-page/components/Benefits.js
Normal file
@ -0,0 +1,102 @@
|
||||
import classNames from 'classnames'
|
||||
import { Terminal, Layers, Coffee, Code } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
import styles from '../styles/index.module.css'
|
||||
|
||||
const Lang = () => (
|
||||
<>
|
||||
<span className='underline decoration-yellow-500 font-bold'>
|
||||
language
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
|
||||
const Benefit = ({ Icon, title, description }) => (
|
||||
<div className='mb-10 md:mb-0 space-y-4'>
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className={`
|
||||
inline-flex h-8 w-8 rounded-md
|
||||
items-center justify-center
|
||||
text-yellow-500 bg-neutral-700
|
||||
`}
|
||||
>
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
<dt className='ml-4'>
|
||||
{ title }
|
||||
</dt>
|
||||
</div>
|
||||
<p className=''>
|
||||
{ description }
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Benefits = () => {
|
||||
return (
|
||||
<SectionContainer className='space-y-16'>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
Yet another web framework. Except it is
|
||||
a <Lang />.
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
Don't worry, it takes less than 30 minutes to learn.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl className='grid grid-cols-1 lg:grid-cols-3 md:gap-16 lg:gap-x-8 xl:gap-x-24'>
|
||||
<Benefit
|
||||
Icon={Layers}
|
||||
title='Truly full-stack'
|
||||
description={`
|
||||
When we say full-stack, we really mean it. Wasp has you covered from front-end,
|
||||
back-end and database to deployment. Zero config required to get started.
|
||||
`}
|
||||
/>
|
||||
|
||||
<Benefit
|
||||
Icon={Coffee}
|
||||
title='The wheel can take a break'
|
||||
description={`
|
||||
No reinventing the wheel here. Write your code in React & Node.js as you are used to,
|
||||
along with your favourite NPM packages.
|
||||
`}
|
||||
/>
|
||||
|
||||
<Benefit
|
||||
Icon={Code}
|
||||
title='Less boilerplate'
|
||||
description={`
|
||||
The language approach allows us to immensely improve developer experience.
|
||||
E.g., full-stack auth takes only 5 lines of code.
|
||||
`}
|
||||
/>
|
||||
</dl>
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const BenefitsWithSkewedBorder = () => (
|
||||
<div className='relative'>
|
||||
<div className={classNames(styles.sectionSkewedContainer)}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.sectionSkewed,
|
||||
'border-t border-b border-yellow-500/25 bg-neutral-100/50'
|
||||
)}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<Benefits />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default BenefitsWithSkewedBorder
|
44
project-page/components/DarkModeToggle.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { useState } from 'react'
|
||||
import { Sun, Moon } from 'react-feather'
|
||||
|
||||
const DarkModeToggle = () => {
|
||||
const [isDarkMode, setIsDarkMode] = useState(false)
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(!isDarkMode)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-end'>
|
||||
<Sun strokeWidth={2} size={22} className='text-neutral-500' />
|
||||
<button
|
||||
type='button'
|
||||
aria-pressed='false'
|
||||
className={`
|
||||
relative inline-flex
|
||||
h-6 w-11 mx-3 flex-shrink-0 cursor-pointer
|
||||
rounded-full border-2 border-transparent
|
||||
bg-neutral-500
|
||||
transition-colors duration-200 ease-in-out focus:outline-none
|
||||
`}
|
||||
onClick={() => toggleDarkMode()}
|
||||
>
|
||||
<span
|
||||
aria-hidden='true'
|
||||
className={`
|
||||
${isDarkMode ? 'translate-x-5' : 'translate-x-0'}
|
||||
inline-block h-5 w-5
|
||||
bg-white shadow-lg rounded-full ring-0
|
||||
transform transition duration-200 ease-in-out
|
||||
`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<Moon strokeWidth={2} size={22} className='text-neutral-500' />
|
||||
</div>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default DarkModeToggle
|
92
project-page/components/Faq.js
Normal file
@ -0,0 +1,92 @@
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { ChevronDown, ChevronRight } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'How is Wasp different from Next.js / Nuxt.js / Gatsby?',
|
||||
answer: <p>
|
||||
Next.js is front-end solution only, focused on static websites. This is some
|
||||
longer text so I can see how it behaves.<br/>
|
||||
|
||||
Maybe I should put here component so I have freedom in formatting, putting URLs, etc?
|
||||
This is some test <Link href='/'><a>url</a></Link>
|
||||
</p>
|
||||
},
|
||||
{
|
||||
question: 'How is Wasp different from Ruby on Rails, Django, etc?',
|
||||
answer: 'Those are all back-end frameworks. Wasp is full-stack!'
|
||||
},
|
||||
{
|
||||
question: 'How hard is it to learn Wasp?',
|
||||
answer: 'Well, it is actually really easy!'
|
||||
},
|
||||
{
|
||||
question: 'Do you support only React currently?',
|
||||
answer: 'Well, it is actually really easy!'
|
||||
}
|
||||
]
|
||||
|
||||
const FaqItem = ({ keyP, faq }) => {
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<div className='py-6'>
|
||||
<dt key={keyP} className='text-base text-neutral-700'>
|
||||
<button
|
||||
className='text-left w-full flex items-center justify-between'
|
||||
onClick={() => { setIsExpanded(!isExpanded) }}
|
||||
>
|
||||
<span>{faq.question}</span>
|
||||
<div className='ml-6 text-yellow-500'>
|
||||
{isExpanded ? (
|
||||
<ChevronDown size={20} />
|
||||
) : (
|
||||
<ChevronRight size={20} />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</dt>
|
||||
{isExpanded && (
|
||||
<dd className='mt-2 text-neutral-500'>
|
||||
{faq.answer}
|
||||
</dd>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Faq = () => {
|
||||
return (
|
||||
<SectionContainer>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
For anything not covered here, join
|
||||
<a
|
||||
href='https://discord.gg/rzdnErX'
|
||||
className='underline decoration-2 decoration-yellow-500 font-medium'
|
||||
>
|
||||
our Discord
|
||||
</a>!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl className='mt-6 max-w-3xl mx-auto divide-y divide-neutral-300'>
|
||||
{faqs.map((faq, idx) => (
|
||||
<FaqItem keyP={idx} key={idx} faq={faq} />
|
||||
))}
|
||||
</dl>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Faq
|
167
project-page/components/Footer.js
Normal file
@ -0,0 +1,167 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
import DarkModeToggle from './DarkModeToggle'
|
||||
|
||||
const docs = [
|
||||
{
|
||||
text: 'Getting Started',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'Todo app tutorial',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'Language reference',
|
||||
url: '/'
|
||||
}
|
||||
]
|
||||
|
||||
const community = [
|
||||
{
|
||||
text: 'Discord',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'Twitter',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'GitHub',
|
||||
url: '/'
|
||||
}
|
||||
]
|
||||
|
||||
const company = [
|
||||
{
|
||||
text: 'Blog',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'Careers',
|
||||
url: '/'
|
||||
},
|
||||
{
|
||||
text: 'Company',
|
||||
url: '/'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO(matija): duplication, I already have Logo in Nav/index.js
|
||||
const Logo = () => (
|
||||
<div className='flex flex-shrink-0 items-center'>
|
||||
<Link href='/' as='/'>
|
||||
<a>
|
||||
<Image
|
||||
src='/wasp-logo.png'
|
||||
width={35}
|
||||
height={35}
|
||||
alt='Wasp Logo'
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<span className='ml-3 font-semibold text-lg text-neutral-700'>
|
||||
Wasp
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Segment = ({ title, links }) => (
|
||||
<div>
|
||||
<h6 className='text-neutral-700'>{title}</h6>
|
||||
<ul className='mt-4 space-y-2'>
|
||||
{links.map((l, idx) => {
|
||||
return (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={l.url}
|
||||
className={`
|
||||
text-sm text-neutral-500 hover:text-neutral-400
|
||||
transition-colors
|
||||
`}
|
||||
>
|
||||
{l.text}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Footer = () => {
|
||||
|
||||
return (
|
||||
<footer className='border-t'>
|
||||
<SectionContainer>
|
||||
<div className='grid grid-cols-1 gap-8 xl:grid xl:grid-cols-3'>
|
||||
|
||||
{/* cols with links */}
|
||||
<div className='grid grid-cols-1 xl:col-span-2'>
|
||||
<div className='grid grid-cols-2 gap-8 md:grid-cols-3'>
|
||||
|
||||
<Segment title='Docs' links={docs} />
|
||||
<Segment title='Community' links={community} />
|
||||
<Segment title='Company' links={company} />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* newsletter part */}
|
||||
<div className='xl:col-span-1'>
|
||||
<h3 className='text-base'>Stay up to date</h3>
|
||||
<p className='mt-4 text-sm text-neutral-500'>
|
||||
Join our mailing list and be the first to know when
|
||||
we ship new features and updates!
|
||||
</p>
|
||||
|
||||
<form className='mt-4 sm:flex sm:max-w-md'>
|
||||
<input
|
||||
type="email" name="email-address" id="email-address"
|
||||
required autoComplete='email'
|
||||
placeholder='you@awesomedev.com'
|
||||
className={`
|
||||
text-sm w-full
|
||||
appearance-none
|
||||
placeholder:text-neutral-400
|
||||
border border-yellow-500
|
||||
px-4 py-2 rounded-md bg-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-yellow-400
|
||||
`}
|
||||
/>
|
||||
<div className='rounded-md mt-3 sm:mt-0 sm:ml-3'>
|
||||
<button type='submit'
|
||||
className={`
|
||||
w-full
|
||||
text-sm border border-transparent rounded-md
|
||||
bg-yellow-500 text-white
|
||||
px-4 py-2
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='pt-8 mt-8'>
|
||||
<Logo />
|
||||
<div className='flex justify-between'>
|
||||
<p className='mt-4 text-xs text-neutral-400'>
|
||||
© Wasp, Inc. All rights reserved.
|
||||
</p>
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
197
project-page/components/Hero.js
Normal file
@ -0,0 +1,197 @@
|
||||
import Link from 'next/link'
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter'
|
||||
import { qtcreatorLight, atomOneLight, atomOneDark, a11ylight } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
|
||||
import { Terminal } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const StartIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
||||
strokeLinejoin="round" opacity="0.5"
|
||||
>
|
||||
<polyline points="13 17 18 12 13 7"></polyline>
|
||||
<polyline points="6 17 11 12 6 7"></polyline>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const ActionButtons = () => (
|
||||
<div className='flex items-center gap-2'>
|
||||
<Link href='/'>
|
||||
<a>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center space-x-2
|
||||
px-3 py-2 rounded
|
||||
bg-yellow-500 text-white text-sm leading-4
|
||||
border border-yellow-500 hover:border-yellow-400
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
<span>Get started in 5 minutes</span>
|
||||
<StartIcon />
|
||||
</button>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href='/'>
|
||||
<a>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center space-x-2
|
||||
px-3 py-2 rounded
|
||||
border border-neutral-500
|
||||
text-sm leading-4
|
||||
hover:text-neutral-400 hover:border-neutral-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
<Terminal size={16} />
|
||||
<span>Showcases</span>
|
||||
</button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
const PHBadge = () => (
|
||||
<a
|
||||
href="https://www.producthunt.com/posts/wasp-lang-alpha?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-wasp-lang-alpha"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
className='w-32 md:w-[180px]'
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=277135&theme=light&period=daily"
|
||||
alt="Wasp-lang Alpha - Develop web apps in React & Node.js with no boilerplate | Product Hunt"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
|
||||
const Hero = () => {
|
||||
const codeString =
|
||||
`app todoApp {
|
||||
title: "ToDo App", /* visible in tab */
|
||||
|
||||
auth: { /* full-stack auth out-of-the-box */
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
/* import your React code */
|
||||
component: import Main from "@ext/Main.js"
|
||||
}`
|
||||
|
||||
return (
|
||||
<SectionContainer className='pb-5 pt-24'>
|
||||
<div className='lg:grid lg:grid-cols-12 lg:gap-16'>
|
||||
|
||||
<div className='lg:col-span-6 space-y-12'>
|
||||
{/* Hero title and subtitle */}
|
||||
<div>
|
||||
<h1
|
||||
className={`
|
||||
text-4xl lg:text-5xl lg:leading-tight
|
||||
font-extrabold text-neutral-700
|
||||
dark:text-yellow-500
|
||||
`}
|
||||
>
|
||||
Develop full-stack web apps
|
||||
<span className='underline decoration-yellow-500'>without boilerplate</span>.
|
||||
</h1>
|
||||
|
||||
<p className='mt-4 sm:mt-5 text-xl lg:text-xl text-neutral-500'>
|
||||
Describe common features via Wasp DSL and write the rest in React, Node.js
|
||||
and Prisma.
|
||||
</p>
|
||||
</div> {/* EOF Hero title and subtitle */}
|
||||
|
||||
<ActionButtons />
|
||||
|
||||
<div className='flex flex-col gap-4'>
|
||||
<small className='text-neutral-500 text-xs'>works with</small>
|
||||
|
||||
<div className='flex'>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='/react-logo-gray.svg'
|
||||
alt='React'
|
||||
/>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='/nodejs-logo-gray.svg'
|
||||
alt='Node'
|
||||
/>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='/prisma-logo-gray.svg'
|
||||
alt='Prisma'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='lg:col-span-6 lg:mt-0 mt-16'>
|
||||
<div className='relative flex flex-col items-center justify-center'>
|
||||
|
||||
<div className='bg-yellow-500/10 flex h-6 w-full items-center justify-between rounded-t-md px-2'>
|
||||
<span className='text-sm text-neutral-500'>todoApp.wasp</span>
|
||||
<div className='flex space-x-2'>
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='w-full text-sm shadow-2xl rounded-b-md'>
|
||||
<SyntaxHighlighter
|
||||
language="javascript"
|
||||
style={atomOneLight}
|
||||
customStyle={{
|
||||
borderBottomLeftRadius: '10px',
|
||||
borderBottomRightRadius: '10px',
|
||||
paddingLeft: '15px',
|
||||
}}
|
||||
>
|
||||
{codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div> {/* EOF code block wrapper */}
|
||||
|
||||
</div> {/* EOF wrapper of header + code */}
|
||||
|
||||
</div> {/* EOF col-span-6 */}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='flex justify-center items-center space-x-4 mt-20 mb-10 md:mt-28 md:mb-0'>
|
||||
<PHBadge />
|
||||
<div
|
||||
className={`
|
||||
h-11 border border-transparent border-l-neutral-400/50
|
||||
`}
|
||||
/>
|
||||
<img
|
||||
className='w-32 md:w-[180px]'
|
||||
src='/yc-logo.png'
|
||||
alt='YC'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default Hero
|
103
project-page/components/HowItWorks.js
Normal file
@ -0,0 +1,103 @@
|
||||
import { ArrowRight } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const Feature = ({ title, description, url }) => (
|
||||
<div className='col-span-12 md:col-span-6'>
|
||||
<div className='lg:mt-5'>
|
||||
<dt>
|
||||
<h4 className='mb-4'>
|
||||
<span className='bg-yellow-500/25 px-2 py-1 rounded'>{ title }</span>
|
||||
</h4>
|
||||
<p className='text-neutral-600'>
|
||||
{ description }
|
||||
</p>
|
||||
<TextLink url={url} label='Learn more' />
|
||||
</dt>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const TextLink = ({ url, label }) => (
|
||||
<a href={url}
|
||||
className={`
|
||||
mt-3 block cursor-pointer text-sm
|
||||
text-neutral-600 hover:text-neutral-500
|
||||
`}
|
||||
>
|
||||
<div className='group flex gap-1 items-center'>
|
||||
<span>{label}</span>
|
||||
<div className='transition-all group-hover:ml-0.5'>
|
||||
<span className='text-yellow-600'>
|
||||
<ArrowRight size={14} strokeWidth={2} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
|
||||
const HowItWorks = () => {
|
||||
return (
|
||||
<SectionContainer className='lg:pb-8'>
|
||||
<div className='grid grid-cols-12'>
|
||||
|
||||
<div className='col-span-12 lg:col-span-4'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
What's under the hood? 🚕
|
||||
</h2>
|
||||
<p className='text-neutral-700'>
|
||||
Given <code>.wasp</code> and <code>.js(x)/.css/...</code>, source files,
|
||||
Wasp compiler generates the full source of your web app in
|
||||
the target stack - front-end, back-end and deployment.
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<div className='py-8'>
|
||||
<dl className='grid grid-cols-12 gap-y-4 md:gap-8'>
|
||||
|
||||
<Feature
|
||||
title='Typescript support'
|
||||
url='/'
|
||||
description="JS or TS - mix'n'match as you wish."
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='Wasp CLI'
|
||||
url='/'
|
||||
description='All the handy commands at your fingertips.'
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='LSP for VS Code'
|
||||
url='/'
|
||||
description='Syntax highligthing, go-to-definition, etc. work out-of-the-box.'
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='Deploy anywhere'
|
||||
url='/'
|
||||
description='See our deployment guide for more details.'
|
||||
/>
|
||||
|
||||
</dl>
|
||||
|
||||
</div> {/* EOF Features */}
|
||||
</div>
|
||||
|
||||
<div className='col-span-12 lg:col-span-7 xl:col-span-7 xl:col-start-6'>
|
||||
<img
|
||||
className=''
|
||||
src='/wasp-compilation-diagram.png'
|
||||
alt='React'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default HowItWorks
|
16
project-page/components/Layouts/SectionContainer.js
Normal file
@ -0,0 +1,16 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
const SectionContainer = ({ children, className }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'container mx-auto px-6 py-16 sm:py-18',
|
||||
'md:py-24',
|
||||
'lg:px-16 lg:py-24 xl:px-20',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
export default SectionContainer
|
14
project-page/components/Nav/SocialIcons.js
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
export const DiscordIcon = () => (
|
||||
<svg width='24' height='24' fill='currentColor' viewBox='0 5 30.67 23.25'>
|
||||
<title>Discord</title>
|
||||
<path d='M26.0015 6.9529C24.0021 6.03845 21.8787 5.37198 19.6623 5C19.3833 5.48048 19.0733 6.13144 18.8563 6.64292C16.4989 6.30193 14.1585 6.30193 11.8336 6.64292C11.6166 6.13144 11.2911 5.48048 11.0276 5C8.79575 5.37198 6.67235 6.03845 4.6869 6.9529C0.672601 12.8736 -0.41235 18.6548 0.130124 24.3585C2.79599 26.2959 5.36889 27.4739 7.89682 28.2489C8.51679 27.4119 9.07477 26.5129 9.55525 25.5675C8.64079 25.2265 7.77283 24.808 6.93587 24.312C7.15286 24.1571 7.36986 23.9866 7.57135 23.8161C12.6241 26.1255 18.0969 26.1255 23.0876 23.8161C23.3046 23.9866 23.5061 24.1571 23.7231 24.312C22.8861 24.808 22.0182 25.2265 21.1037 25.5675C21.5842 26.5129 22.1422 27.4119 22.7621 28.2489C25.2885 27.4739 27.8769 26.2959 30.5288 24.3585C31.1952 17.7559 29.4733 12.0212 26.0015 6.9529ZM10.2527 20.8402C8.73376 20.8402 7.49382 19.4608 7.49382 17.7714C7.49382 16.082 8.70276 14.7025 10.2527 14.7025C11.7871 14.7025 13.0425 16.082 13.0115 17.7714C13.0115 19.4608 11.7871 20.8402 10.2527 20.8402ZM20.4373 20.8402C18.9183 20.8402 17.6768 19.4608 17.6768 17.7714C17.6768 16.082 18.8873 14.7025 20.4373 14.7025C21.9717 14.7025 23.2271 16.082 23.1961 17.7714C23.1961 19.4608 21.9872 20.8402 20.4373 20.8402Z'></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TwitterIcon = () => (
|
||||
<svg width='24' height='24' fill='currentColor' viewBox='0 0 335 276'>
|
||||
<title>Discord</title>
|
||||
<path d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'></path>
|
||||
</svg>
|
||||
)
|
187
project-page/components/Nav/index.js
Normal file
@ -0,0 +1,187 @@
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { Star, Twitter } from 'react-feather'
|
||||
|
||||
import { DiscordIcon, TwitterIcon } from './SocialIcons'
|
||||
|
||||
const Nav = () => {
|
||||
|
||||
const Logo = () => (
|
||||
<div className='flex flex-shrink-0 items-center'>
|
||||
<Link href='/' as='/'>
|
||||
<a>
|
||||
<Image
|
||||
src='/wasp-logo.png'
|
||||
width={35}
|
||||
height={35}
|
||||
alt='Wasp Logo'
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<span className='ml-3 font-semibold text-lg text-neutral-700'>
|
||||
Wasp <sup className='text-base text-yellow-500'>βeta</sup>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const SocialIcon = ({ Icon, url }) => (
|
||||
<a href={url} target='_blank' rel='noreferrer'
|
||||
className={`
|
||||
hidden lg:flex hover:opacity-75 py-5
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
`}
|
||||
>
|
||||
<Icon />
|
||||
</a>
|
||||
)
|
||||
|
||||
const GitHubButton = () => (
|
||||
<a href='https://github.com/wasp-lang/wasp' target='_blank' rel='noreferrer'
|
||||
className={`
|
||||
px-2.5 py-1 rounded
|
||||
hover:bg-neutral-200
|
||||
transition ease-out duration-200
|
||||
hidden lg:flex items-center space-x-2 text-xs
|
||||
group
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex h-3 w-3 items-center justify-center
|
||||
group-hover:h-4 group-hover:w-4
|
||||
group-hover:text-yellow-500
|
||||
`}
|
||||
>
|
||||
<Star strokeWidth={2} />
|
||||
</div>
|
||||
<span className='truncate'>Star us on GitHub</span>
|
||||
</a>
|
||||
)
|
||||
|
||||
const HamburgerButton = () => (
|
||||
<div className='absolute inset-y-0 left-0 px-2 flex items-center lg:hidden'>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center p-2
|
||||
rounded-md hover:bg-gray-50
|
||||
focus:ring-yellow-500 focus:outline-none focus:ring-2 focus:ring-inset
|
||||
`}
|
||||
>
|
||||
|
||||
<span className="sr-only">Open menu</span>
|
||||
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='sticky top-0 z-50'>
|
||||
<div className='bg-[#f5f4f0] absolute top-0 h-full w-full opacity-80'></div>
|
||||
<nav className='border-b backdrop-blur-sm'>
|
||||
<div className='
|
||||
relative mx-auto
|
||||
flex justify-between
|
||||
h-16
|
||||
lg:container lg:px-16 xl:px-20
|
||||
'
|
||||
>
|
||||
<HamburgerButton />
|
||||
<div className='
|
||||
flex flex-1
|
||||
items-center justify-center
|
||||
sm:items-stretch
|
||||
lg:justify-between
|
||||
'
|
||||
>
|
||||
<div className='flex items-center'> {/* Navbar left side */}
|
||||
<Logo />
|
||||
|
||||
<div className='hidden pl-4 sm:ml-6 sm:space-x-4 lg:flex'> {/* Left items */}
|
||||
<Link href='/docs'>
|
||||
<a
|
||||
className={`
|
||||
p-5 px-1
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
text-sm font-medium
|
||||
`}
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
</Link>
|
||||
<Link href='/blog'>
|
||||
<a
|
||||
className={`
|
||||
p-5 px-1
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
text-sm font-medium
|
||||
`}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
</div> {/* EOF left items */}
|
||||
</div> {/* EOF left side */}
|
||||
|
||||
<div className='flex items-center gap-2 space-x-2'> {/* Navbar right side */}
|
||||
<GitHubButton />
|
||||
|
||||
<Link href='/docs'>
|
||||
<a>
|
||||
<button
|
||||
className={`
|
||||
hidden lg:block text-xs
|
||||
px-2.5 py-1 rounded
|
||||
bg-yellow-500 text-white
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Get started
|
||||
</button>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<SocialIcon
|
||||
Icon={ DiscordIcon }
|
||||
url='https://discord.gg/rzdnErX'
|
||||
/>
|
||||
<SocialIcon
|
||||
Icon={ TwitterIcon }
|
||||
url='https://twitter.com/WaspLang'
|
||||
/>
|
||||
</div> {/* EOF right side */}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default Nav
|
108
project-page/components/ShowcaseGallery.js
Normal file
@ -0,0 +1,108 @@
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const Tag = ({ text, className }) => (
|
||||
<span
|
||||
className={classNames(`
|
||||
text-sm border rounded-md px-2.5 py-0.5
|
||||
|
||||
`, className
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
|
||||
const ShowcaseItem = ({ url, thumb, title, description, children }) => (
|
||||
<div>
|
||||
<a href={url}>
|
||||
<div className='group inline-block min-w-full'>
|
||||
<div className='flex flex-col space-y-3 pb-8 md:pb-0'>
|
||||
|
||||
<div
|
||||
className={`
|
||||
border-neutral-300 relative mb-4 h-60 w-full overflow-auto
|
||||
rounded-lg border shadow-lg
|
||||
`}
|
||||
>
|
||||
<Image
|
||||
layout='fill'
|
||||
src={thumb}
|
||||
objectFit='cover'
|
||||
className=''
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 className='text-neutral-700 text-xl'>{title}</h3>
|
||||
|
||||
<div className='flex space-x-2'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<p className='text-neutral-500 text-base'>{description}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
const ShowcaseGallery = () => {
|
||||
return (
|
||||
<SectionContainer className='space-y-16'>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
🏆 Showcase Gallery 🏆
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
See what others are building with Wasp.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
mx-auto grid max-w-lg gap-8
|
||||
lg:max-w-none lg:grid-cols-3 lg:gap-12
|
||||
`}
|
||||
>
|
||||
<ShowcaseItem
|
||||
url='/'
|
||||
thumb='/showcase/farnance-dashboard.png'
|
||||
title='Farnance: SaaS marketplace for farmers'
|
||||
description="See how Julian won HackLBS 2021 among 250 participants and why Wasp was instrumental for the team's victory."
|
||||
>
|
||||
<Tag text='hackathon' className='text-yellow-600 border-yellow-600 bg-yellow-50' />
|
||||
<Tag text='material-ui' className='text-blue-500 border-blue-500 bg-slate-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
<ShowcaseItem
|
||||
url='/'
|
||||
thumb='/showcase/grabbit-hero.png'
|
||||
title='Grabbit: Easily manage dev environments'
|
||||
description='See how Michael built and deployed an internal tool for managing dev resources at StudentBeans.'
|
||||
>
|
||||
<Tag text='internal-tools' className='text-green-600 border-green-600 bg-green-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
<ShowcaseItem
|
||||
url='/'
|
||||
thumb='/showcase/amicus-landing.png'
|
||||
title='Amicus: Task and workflow management for legal teams'
|
||||
description='See how Erlis rolled out fully-fledged SaaS as a team of one in record time and got first paying customers.'
|
||||
>
|
||||
<Tag text='startup' className='text-fuchsia-600 border-fuchsia-600 bg-fuchsia-50' />
|
||||
<Tag text='material-ui' className='text-blue-500 border-blue-500 bg-slate-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowcaseGallery
|
5
project-page/jsconfig.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
7
project-page/next.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
6691
project-page/package-lock.json
generated
Normal file
26
project-page/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "project-page",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.2",
|
||||
"next": "12.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-syntax-highlighter": "^15.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"postcss": "^8.4.18",
|
||||
"tailwindcss": "^3.2.0"
|
||||
}
|
||||
}
|
7
project-page/pages/_app.js
Normal file
@ -0,0 +1,7 @@
|
||||
import '../styles/globals.css'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
5
project-page/pages/api/hello.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
|
||||
export default function handler(req, res) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
57
project-page/pages/index.js
Normal file
@ -0,0 +1,57 @@
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Nav from 'components/Nav/index'
|
||||
import Hero from 'components/Hero'
|
||||
import Benefits from 'components/Benefits'
|
||||
import HowItWorks from 'components/HowItWorks'
|
||||
import ShowcaseGallery from 'components/ShowcaseGallery'
|
||||
import Faq from 'components/Faq'
|
||||
import Footer from 'components/Footer'
|
||||
|
||||
import styles from '../styles/index.module.css'
|
||||
|
||||
|
||||
const Background = () => {
|
||||
return (
|
||||
<div className='absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none'>
|
||||
<span className={classNames(styles.leftLights, "opacity-100")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const LightsTwo = () => (
|
||||
<div className='absolute top-[1800px] lg:top-[1000px] left-0 w-full h-full overflow-hidden pointer-events-none'>
|
||||
<span className={classNames(styles.lightsTwo, "opacity-100")} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const Index = () => {
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div className='min-h-screen'>
|
||||
<main>
|
||||
<Background />
|
||||
<div> {/* container */}
|
||||
|
||||
<Hero />
|
||||
<Benefits />
|
||||
|
||||
<LightsTwo />
|
||||
|
||||
<HowItWorks />
|
||||
<ShowcaseGallery />
|
||||
<Faq />
|
||||
|
||||
</div> {/* eof container */}
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Index
|
6
project-page/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
project-page/public/favicon.ico
Normal file
After Width: | Height: | Size: 25 KiB |
6
project-page/public/nodejs-logo-gray.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="138" height="40" viewBox="0 0 138 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M126.684 14.3582C126.349 14.3582 126.023 14.4274 125.735 14.5907L116.604 19.8634C116.013 20.204 115.654 20.8468 115.654 21.5309V32.0573C115.654 32.7408 116.013 33.3829 116.604 33.7243L118.989 35.1008C120.147 35.6717 120.574 35.6628 121.102 35.6628C122.817 35.6628 123.796 34.622 123.796 32.8133V22.4225C123.796 22.2756 123.669 22.1706 123.525 22.1706H122.381C122.235 22.1706 122.11 22.2756 122.11 22.4225V32.8133C122.11 33.6149 121.275 34.4213 119.919 33.7437L117.438 32.2898C117.35 32.2421 117.283 32.1578 117.283 32.0573V21.5307C117.283 21.431 117.349 21.3294 117.438 21.2787L126.549 16.0257C126.59 16.0016 126.637 15.9889 126.685 15.9889C126.732 15.9889 126.779 16.0016 126.82 16.0257L135.932 21.279C136.018 21.3313 136.087 21.428 136.087 21.5312V32.0576C136.087 32.1581 136.036 32.261 135.951 32.3095L126.82 37.5628C126.742 37.6095 126.634 37.6095 126.549 37.5628L124.203 36.1672C124.168 36.1478 124.128 36.1377 124.087 36.1377C124.046 36.1377 124.006 36.1478 123.971 36.1672C123.323 36.5342 123.202 36.5767 122.594 36.7874C122.445 36.8397 122.218 36.9208 122.672 37.1754L125.735 38.9779C126.024 39.1445 126.351 39.2315 126.685 39.2301C127.018 39.2322 127.346 39.1452 127.635 38.9782L136.765 33.7245C137.356 33.381 137.715 32.7411 137.715 32.0576V21.5309C137.715 20.8471 137.356 20.2053 136.765 19.864L127.635 14.5912C127.348 14.4277 127.02 14.3584 126.685 14.3584L126.684 14.3582Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path d="M129.127 21.8796C126.527 21.8796 124.979 22.9862 124.979 24.8264C124.979 26.8223 126.516 27.3709 129.011 27.6177C131.997 27.91 132.229 28.3484 132.229 28.936C132.229 29.9553 131.417 30.3899 129.496 30.3899C127.083 30.3899 126.553 29.7874 126.375 28.5871C126.354 28.4583 126.255 28.3543 126.123 28.3543H124.94C124.795 28.3543 124.669 28.4799 124.669 28.6259C124.669 30.1625 125.505 31.9795 129.496 31.9795C132.385 31.9795 134.052 30.8455 134.052 28.8584C134.052 26.8889 132.706 26.36 129.903 25.9895C127.071 25.6147 126.801 25.4313 126.801 24.7682C126.801 24.2207 127.03 23.4884 129.127 23.4884C131.001 23.4884 131.694 23.8926 131.977 25.1559C132.002 25.2745 132.107 25.369 132.229 25.369H133.412C133.485 25.369 133.555 25.3243 133.606 25.272C133.655 25.2163 133.69 25.1535 133.683 25.0783C133.5 22.9029 132.049 21.8796 129.127 21.8796Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path d="M73.0059 0.555789C72.9038 0.555857 72.8036 0.582507 72.7149 0.633116C72.6269 0.68465 72.5537 0.758239 72.5027 0.846634C72.4518 0.935029 72.4247 1.03518 72.4242 1.13722V15.9672C72.4242 16.1127 72.3566 16.2431 72.2305 16.3161C72.1687 16.3521 72.0984 16.3711 72.0269 16.3711C71.9554 16.3711 71.8852 16.3521 71.8234 16.3161L69.4001 14.9205C69.2234 14.8182 69.0228 14.7643 68.8186 14.7643C68.6144 14.7643 68.4137 14.8182 68.237 14.9205L58.5636 20.5033C58.2021 20.7121 57.9822 21.1133 57.9822 21.5309V32.6969C57.9822 33.114 58.2023 33.4961 58.5636 33.7049L68.237 39.288C68.4137 39.3905 68.6143 39.4445 68.8186 39.4445C69.0228 39.4445 69.2235 39.3905 69.4001 39.288L79.0735 33.7049C79.2506 33.6029 79.3976 33.4561 79.4998 33.2791C79.6019 33.1021 79.6555 32.9013 79.6552 32.6969V4.8594C79.6549 4.65027 79.5992 4.44495 79.4936 4.26442C79.388 4.08388 79.2365 3.93459 79.0543 3.83179L73.2966 0.613717C73.2064 0.563333 73.1061 0.554442 73.0059 0.555789ZM10.7783 14.494C10.594 14.5012 10.4205 14.5336 10.2551 14.6295L0.581435 20.2126C0.404234 20.3144 0.257114 20.4613 0.154999 20.6383C0.0528854 20.8153 -0.000587913 21.0162 4.87546e-06 21.2206L0.0194039 36.206C0.0194039 36.4143 0.126907 36.608 0.310119 36.7098C0.397956 36.7626 0.498497 36.7905 0.600969 36.7905C0.703441 36.7905 0.803983 36.7626 0.891819 36.7098L6.62988 33.4144C6.99334 33.1983 7.23071 32.8228 7.23071 32.4062V25.408C7.23071 24.9904 7.45029 24.6073 7.81241 24.3998L10.2548 22.9848C10.4314 22.8821 10.6322 22.8285 10.8365 22.8296C11.0359 22.8296 11.2406 22.8794 11.4179 22.9848L13.8606 24.3998C14.0379 24.5015 14.1852 24.6484 14.2874 24.8255C14.3896 25.0026 14.443 25.2036 14.4423 25.408V32.406C14.4423 32.8225 14.6821 33.2008 15.0431 33.4142L20.7812 36.7098C20.869 36.7626 20.9695 36.7905 21.072 36.7905C21.1745 36.7905 21.275 36.7626 21.3629 36.7098C21.4513 36.6587 21.5247 36.5852 21.5758 36.4967C21.6268 36.4082 21.6538 36.3079 21.6538 36.2057L21.6727 21.2206C21.6739 21.0161 21.6206 20.815 21.5184 20.6379C21.4163 20.4608 21.2689 20.314 21.0913 20.2126L11.4179 14.6295C11.2544 14.5336 11.0803 14.5015 10.8947 14.494H10.7783ZM97.703 14.6295C97.5009 14.6295 97.3029 14.6807 97.1215 14.7847L87.4482 20.3678C87.271 20.4697 87.124 20.6166 87.0218 20.7936C86.9197 20.9706 86.8661 21.1714 86.8665 21.3757V32.5417C86.8665 32.9618 87.103 33.3422 87.4673 33.55L97.0633 39.0167C97.4176 39.2188 97.8503 39.2341 98.2071 39.0355L104.023 35.7983C104.207 35.696 104.332 35.5047 104.333 35.2945C104.334 35.1922 104.307 35.0914 104.256 35.0028C104.205 34.9141 104.131 34.8408 104.042 34.7904L94.3106 29.2073C94.1284 29.1033 94.0005 28.8928 94.0005 28.6838V25.1946C94.0005 24.9858 94.1298 24.7945 94.3106 24.6905L97.3347 22.9457C97.423 22.8946 97.5234 22.8676 97.6255 22.8676C97.7276 22.8676 97.828 22.8946 97.9164 22.9457L100.94 24.6905C101.029 24.7414 101.102 24.8148 101.153 24.9033C101.205 24.9918 101.231 25.0922 101.231 25.1944V27.9471C101.231 28.0493 101.258 28.1497 101.309 28.2382C101.36 28.3267 101.433 28.4001 101.522 28.451C101.703 28.5558 101.923 28.5561 102.103 28.451L107.9 25.0785C108.077 24.9762 108.223 24.8292 108.326 24.6523C108.428 24.4753 108.481 24.2746 108.482 24.0703V21.376C108.482 20.9597 108.261 20.5769 107.9 20.3678L98.2847 14.7847C98.1079 14.6824 97.9072 14.629 97.703 14.6297V14.6295ZM68.7796 22.9654C68.8303 22.9654 68.8896 22.9778 68.9348 23.0039L72.2499 24.9231C72.3399 24.9751 72.4051 25.0713 72.4051 25.175V29.0133C72.4051 29.1176 72.3404 29.2135 72.2499 29.2655L68.9348 31.1846C68.8905 31.2099 68.8404 31.2232 68.7895 31.2232C68.7385 31.2232 68.6884 31.2099 68.6441 31.1846L65.3293 29.2655C65.239 29.2132 65.1741 29.1176 65.1741 29.0133V25.175C65.1741 25.071 65.2393 24.9756 65.3293 24.9231L68.6441 23.0042C68.6849 22.9788 68.7319 22.9655 68.7799 22.9657V22.9654H68.7796Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path d="M39.837 14.5709C39.6354 14.5709 39.4361 14.6221 39.2555 14.7261L29.5822 20.2899C29.2203 20.4981 29.0005 20.9001 29.0005 21.3172V32.4832C29.0005 32.9008 29.2206 33.2823 29.5822 33.4914L39.2555 39.0743C39.4322 39.1769 39.6328 39.2309 39.8371 39.2309C40.0414 39.2309 40.242 39.1769 40.4187 39.0743L50.092 33.4914C50.2691 33.3894 50.4161 33.2425 50.5182 33.0654C50.6203 32.8884 50.6739 32.6876 50.6735 32.4832V21.3172C50.6735 20.8996 50.4536 20.4981 50.092 20.2899L40.4187 14.7261C40.2419 14.624 40.0412 14.5705 39.837 14.5709ZM97.6834 24.5353C97.6449 24.5353 97.602 24.5353 97.5673 24.5547L95.7063 25.6402C95.6716 25.6595 95.6426 25.6876 95.6221 25.7216C95.6017 25.7557 95.5906 25.7945 95.5899 25.8342V27.9665C95.5899 28.0468 95.6363 28.1203 95.7063 28.1605L97.5673 29.2266C97.637 29.267 97.7114 29.267 97.7804 29.2266L99.6413 28.1602C99.6759 28.1408 99.7048 28.1127 99.7252 28.0788C99.7456 28.0448 99.7568 28.0061 99.7577 27.9665V25.8339C99.7568 25.7943 99.7456 25.7556 99.7252 25.7216C99.7048 25.6876 99.6759 25.6596 99.6413 25.6402L97.7804 24.5544C97.7459 24.5342 97.7222 24.5355 97.6834 24.5355V24.5353Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
6
project-page/public/prisma-logo-gray.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="114" height="46" viewBox="0 0 114 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M69.8179 30.5522H72.8172V19.5584H69.8179V30.5522ZM69.6904 16.7164C69.6904 15.7397 70.2345 15.2515 71.3227 15.2515C72.4106 15.2515 72.955 15.7397 72.955 16.7164C72.955 17.182 72.8189 17.5442 72.547 17.8032C72.2748 18.0622 71.8668 18.1914 71.3227 18.1914C70.2345 18.1914 69.6904 17.6997 69.6904 16.7164Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M57.6831 24.2095C58.6504 23.39 59.1338 22.207 59.1338 20.6598C59.1338 19.1716 58.6799 18.0522 57.772 17.3015C56.864 16.5515 55.5383 16.1758 53.7942 16.1758H49.2021V30.5522H52.2504V25.4387H53.5584C55.3414 25.4387 56.7164 25.0293 57.6831 24.2095ZM53.2535 22.9411H52.2504V18.6737H53.6369C54.4695 18.6737 55.0806 18.8438 55.4709 19.1848C55.8608 19.5258 56.056 20.0536 56.056 20.7682C56.056 21.4758 55.8233 22.015 55.3577 22.3855C54.8921 22.756 54.191 22.9411 53.2535 22.9411Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path d="M68.2271 19.4403C67.9583 19.3813 67.6208 19.3518 67.2143 19.3518C66.5323 19.3518 65.9014 19.5403 65.3215 19.9171C64.7441 20.291 64.2691 20.8031 63.9396 21.407H63.7924L63.3497 19.5584H61.0781V30.5522H64.0774V24.957C64.0774 24.072 64.3444 23.3834 64.8785 22.8917C65.4132 22.4001 66.1587 22.1542 67.116 22.1542C67.4636 22.1542 67.7583 22.1872 68.0011 22.2525L68.2271 19.4403ZM82.2011 29.864C82.9848 29.2737 83.3764 28.4154 83.3764 27.2876C83.3764 26.7435 83.2817 26.2747 83.0914 25.8813C82.9014 25.4879 82.606 25.1407 82.2063 24.839C81.8066 24.5376 81.1771 24.2133 80.3181 23.8657C79.3546 23.4789 78.7303 23.1869 78.4452 22.9904C78.1601 22.7938 78.0171 22.5612 78.0171 22.2921C78.0171 21.8136 78.4598 21.5744 79.3448 21.5744C79.8428 21.5744 80.3313 21.6497 80.8098 21.8001C81.2882 21.9515 81.8032 22.1445 82.3539 22.3806L83.2584 20.2174C82.0063 19.6407 80.7181 19.3522 79.3938 19.3522C78.0039 19.3522 76.9309 19.6192 76.174 20.1535C75.416 20.6879 75.0375 21.4431 75.0375 22.4199C75.0375 22.9904 75.1278 23.4706 75.3084 23.8605C75.4882 24.2508 75.7768 24.5966 76.174 24.898C76.5698 25.1997 77.1917 25.5275 78.0368 25.8813C78.6268 26.1306 79.099 26.3484 79.4532 26.5352C79.807 26.7223 80.0563 26.8893 80.2007 27.0369C80.3448 27.1845 80.4167 27.3761 80.4167 27.6122C80.4167 28.2414 79.8726 28.556 78.7841 28.556C78.2532 28.556 77.6386 28.4675 76.9407 28.2907C76.242 28.1136 75.6146 27.8942 75.0573 27.6317V30.1098C75.5666 30.3249 76.0974 30.4847 76.6407 30.5866C77.2045 30.6949 77.8862 30.7491 78.6862 30.7491C80.2459 30.7491 81.4178 30.4539 82.2011 29.8636V29.864ZM95.2581 30.5522H92.2588V24.131C92.2588 23.3379 92.1258 22.7428 91.8608 22.3463C91.5949 21.9497 91.1772 21.7511 90.6067 21.7511C89.84 21.7511 89.2827 22.0331 88.9351 22.597C88.5876 23.1608 88.414 24.0883 88.414 25.3799V30.5522H85.4146V19.5584H87.706L88.1091 20.9647H88.2765C88.5712 20.4598 88.9976 20.0647 89.5546 19.7796C90.1119 19.4945 90.7511 19.3518 91.4723 19.3518C93.1178 19.3518 94.232 19.8897 94.8157 20.9647H95.081C95.3761 20.4532 95.8105 20.0567 96.3841 19.7747C96.9577 19.4928 97.6049 19.3518 98.3261 19.3518C99.5716 19.3518 100.514 19.6716 101.153 20.3105C101.792 20.9497 102.112 21.974 102.112 23.3834V30.5522H99.1029V24.131C99.1029 23.3379 98.9699 22.7428 98.7049 22.3463C98.439 21.9497 98.0213 21.7511 97.4508 21.7511C96.7168 21.7511 96.1678 22.0136 95.8036 22.5379C95.44 23.0626 95.2581 23.8949 95.2581 25.0355V30.5522Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M111.144 29.0572L111.725 30.552H113.819V23.2259C113.819 21.9148 113.426 20.9381 112.639 20.2957C111.852 19.6533 110.722 19.3318 109.247 19.3318C107.706 19.3318 106.303 19.6631 105.038 20.3252L106.031 22.3509C107.217 21.8197 108.25 21.5544 109.129 21.5544C110.269 21.5544 110.84 22.1117 110.84 23.2259V23.7079L108.932 23.7669C107.287 23.8259 106.056 24.1325 105.239 24.6863C104.423 25.2405 104.015 26.1009 104.015 27.2676C104.015 28.3822 104.318 29.2409 104.925 29.844C105.531 30.4471 106.362 30.7485 107.418 30.7485C108.276 30.7485 108.973 30.6256 109.507 30.3798C110.041 30.1339 110.561 29.6933 111.066 29.0572H111.144ZM109.679 25.4975L110.84 25.4582V26.3627C110.84 27.0252 110.631 27.5561 110.215 27.9558C109.799 28.3558 109.243 28.5558 108.548 28.5558C107.578 28.5558 107.093 28.1329 107.093 27.2874C107.093 26.6971 107.306 26.2582 107.732 25.9697C108.158 25.6815 108.807 25.5238 109.679 25.4975ZM33.8912 36.0308L13.9332 41.9336C13.3234 42.1142 12.7391 41.5867 12.8672 40.9718L19.9971 6.82588C20.1304 6.18769 21.013 6.0863 21.2915 6.67762L34.4929 34.711C34.7419 35.2398 34.4579 35.8634 33.8912 36.0308ZM37.3138 34.6384L22.0283 2.17934V2.17865C21.8384 1.77895 21.5438 1.43809 21.1759 1.19222C20.808 0.946358 20.3803 0.804647 19.9384 0.782117C19.0082 0.72795 18.1748 1.17656 17.7047 1.94011L1.12687 28.7909C0.875024 29.1965 0.744005 29.6655 0.749171 30.1429C0.754336 30.6203 0.895473 31.0863 1.15604 31.4864L9.25953 44.0392C9.74252 44.7885 10.5793 45.2222 11.4491 45.2222C11.6956 45.2222 11.9436 45.1874 12.1873 45.1152L35.7096 38.1582C36.0637 38.0545 36.3914 37.876 36.6705 37.6347C36.9497 37.3934 37.1738 37.095 37.3277 36.7596C37.4796 36.4262 37.557 36.0637 37.5546 35.6973C37.5522 35.331 37.47 34.9695 37.3138 34.6381V34.6384Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
4
project-page/public/react-logo-gray.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="57" height="50" viewBox="0 0 57 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M56.3048 25.0057C56.3048 21.2863 51.6469 17.7615 44.5057 15.5756C46.1537 8.29709 45.4213 2.50629 42.194 0.652323C41.4501 0.217441 40.5803 0.0114443 39.6305 0.0114443V2.56352C40.1569 2.56352 40.5803 2.66651 40.9351 2.86107C42.4915 3.75372 43.1667 7.15267 42.6403 11.5244C42.5144 12.6001 42.3084 13.7331 42.0566 14.889C39.8136 14.3397 37.3645 13.9162 34.7895 13.6416C33.2446 11.5244 31.6424 9.60174 30.0287 7.91943C33.7595 4.45182 37.2615 2.55207 39.6419 2.55207V0C36.4947 0 32.3748 2.24308 28.2091 6.13413C24.0434 2.26596 19.9234 0.0457771 16.7763 0.0457771V2.59785C19.1452 2.59785 22.6586 4.48615 26.3894 7.93088C24.7872 9.61318 23.185 11.5244 21.663 13.6416C19.0766 13.9162 16.6275 14.3397 14.3844 14.9004C14.1212 13.756 13.9266 12.6459 13.7893 11.5816C13.2514 7.20989 13.9152 3.81094 15.4602 2.90684C15.8035 2.70085 16.2498 2.60929 16.7763 2.60929V0.0572213C15.8149 0.0572213 14.9452 0.263218 14.1899 0.6981C10.974 2.55207 10.253 8.33143 11.9124 15.5871C4.79411 17.7844 0.15918 21.2978 0.15918 25.0057C0.15918 28.7251 4.817 32.2499 11.9582 34.4358C10.3102 41.7144 11.0427 47.5051 14.27 49.3591C15.0138 49.794 15.8836 50 16.8449 50C19.9921 50 24.112 47.7569 28.2777 43.8659C32.4435 47.734 36.5634 49.9542 39.7106 49.9542C40.6719 49.9542 41.5416 49.7482 42.297 49.3133C45.5128 47.4594 46.2338 41.68 44.5744 34.4244C51.6698 32.2385 56.3048 28.7137 56.3048 25.0057ZM41.4043 17.3724C40.9809 18.8487 40.4544 20.3708 39.8593 21.8929C39.3901 20.9773 38.898 20.0618 38.3601 19.1463C37.8337 18.2307 37.2729 17.3381 36.7122 16.4683C38.3373 16.7086 39.9051 17.0062 41.4043 17.3724ZM36.1628 29.5605C35.2702 31.1055 34.3547 32.5704 33.4048 33.9323C31.6996 34.081 29.9715 34.1611 28.232 34.1611C26.5039 34.1611 24.7758 34.081 23.082 33.9437C22.1322 32.5818 21.2052 31.1284 20.3125 29.5949C19.4428 28.0957 18.6531 26.5736 17.9321 25.0401C18.6417 23.5065 19.4428 21.973 20.3011 20.4738C21.1937 18.9288 22.1093 17.464 23.0592 16.1021C24.7644 15.9533 26.4924 15.8732 28.232 15.8732C29.9601 15.8732 31.6881 15.9533 33.3819 16.0906C34.3318 17.4525 35.2587 18.9059 36.1514 20.4395C37.0212 21.9387 37.8108 23.4607 38.5318 24.9943C37.8108 26.5278 37.0212 28.0613 36.1628 29.5605ZM39.8593 28.0728C40.4773 29.6063 41.0038 31.1399 41.4387 32.6276C39.9395 32.9938 38.3601 33.3028 36.7236 33.5431C37.2844 32.6619 37.8452 31.7578 38.3716 30.8309C38.898 29.9153 39.3901 28.9883 39.8593 28.0728ZM28.2549 40.2838C27.1905 39.1852 26.1262 37.9606 25.0734 36.6217C26.1033 36.6674 27.1562 36.7018 28.2205 36.7018C29.2963 36.7018 30.3606 36.6789 31.402 36.6217C30.372 37.9606 29.3077 39.1852 28.2549 40.2838ZM19.7403 33.5431C18.1152 33.3028 16.5474 33.0053 15.0482 32.639C15.4716 31.1627 15.998 29.6407 16.5931 28.1186C17.0624 29.0341 17.5545 29.9496 18.0923 30.8652C18.6302 31.7807 19.1796 32.6734 19.7403 33.5431ZM28.1976 9.72763C29.262 10.8263 30.3263 12.0508 31.3791 13.3898C30.3492 13.344 29.2963 13.3097 28.232 13.3097C27.1562 13.3097 26.0919 13.3326 25.0505 13.3898C26.0804 12.0508 27.1448 10.8263 28.1976 9.72763ZM19.7289 16.4683C19.1681 17.3495 18.6073 18.2536 18.0809 19.1806C17.5545 20.0961 17.0624 21.0117 16.5931 21.9272C15.9752 20.3937 15.4487 18.8602 15.0138 17.3724C16.513 17.0176 18.0923 16.7086 19.7289 16.4683ZM9.37181 30.7965C5.32054 29.0684 2.69981 26.8025 2.69981 25.0057C2.69981 23.209 5.32054 20.9316 9.37181 19.2149C10.356 18.7915 11.4318 18.4138 12.5419 18.0591C13.1942 20.3021 14.0525 22.6368 15.1168 25.0286C14.064 27.409 13.2171 29.7322 12.5762 31.9638C11.4432 31.6091 10.3675 31.22 9.37181 30.7965ZM15.5288 47.1504C13.9724 46.2577 13.2972 42.8588 13.8236 38.4871C13.9495 37.4113 14.1555 36.2783 14.4073 35.1225C16.6504 35.6718 19.0994 36.0952 21.6744 36.3699C23.2194 38.4871 24.8216 40.4097 26.4352 42.092C22.7044 45.5596 19.2024 47.4594 16.822 47.4594C16.307 47.4479 15.8722 47.3449 15.5288 47.1504ZM42.6746 38.4298C43.2125 42.8016 42.5487 46.2005 41.0038 47.1046C40.6604 47.3106 40.2141 47.4022 39.6877 47.4022C37.3187 47.4022 33.8053 45.5139 30.0745 42.0691C31.6767 40.3868 33.2789 38.4756 34.801 36.3584C37.3874 36.0838 39.8365 35.6603 42.0795 35.0996C42.3428 36.2554 42.5487 37.3655 42.6746 38.4298ZM47.0807 30.7965C46.0965 31.22 45.0207 31.5976 43.9106 31.9524C43.2583 29.7093 42.4 27.3747 41.3357 24.9828C42.3885 22.6024 43.2354 20.2792 43.8763 18.0476C45.0093 18.4024 46.085 18.7915 47.0921 19.2149C51.1434 20.943 53.7641 23.209 53.7641 25.0057C53.7527 26.8025 51.1319 29.0799 47.0807 30.7965Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
<path d="M28.2203 30.2361C31.1087 30.2361 33.4503 27.8945 33.4503 25.006C33.4503 22.1176 31.1087 19.776 28.2203 19.776C25.3318 19.776 22.9902 22.1176 22.9902 25.006C22.9902 27.8945 25.3318 30.2361 28.2203 30.2361Z" fill="#2F2F2F" fill-opacity="0.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
BIN
project-page/public/showcase/amicus-landing.png
Normal file
After Width: | Height: | Size: 284 KiB |
BIN
project-page/public/showcase/farnance-dashboard.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
project-page/public/showcase/grabbit-hero.png
Normal file
After Width: | Height: | Size: 31 KiB |
4
project-page/public/vercel.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
project-page/public/wasp-compilation-diagram.png
Normal file
After Width: | Height: | Size: 166 KiB |
202
project-page/public/wasp-compilation-diagram.svg
Normal file
After Width: | Height: | Size: 473 KiB |
BIN
project-page/public/wasp-logo.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
project-page/public/yc-logo.png
Normal file
After Width: | Height: | Size: 57 KiB |
131
project-page/styles/Home.module.css
Normal file
@ -0,0 +1,131 @@
|
||||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
*/
|
70
project-page/styles/globals.css
Normal file
@ -0,0 +1,70 @@
|
||||
@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;
|
||||
|
||||
/* Removes blue tap box over Button/Link on mobile.
|
||||
* Issue on Tailwind's repo: https://github.com/tailwindlabs/tailwindcss/discussions/2984
|
||||
*/
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
/*
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
*/
|
||||
|
||||
/*
|
||||
font-family: Inter;
|
||||
*/
|
||||
|
||||
/* Fading out dots */
|
||||
/*
|
||||
background: linear-gradient(180deg,hsla(0,0%,100%,0) 0,#fff 300px),
|
||||
fixed 0 0 /20px 20px radial-gradient(#d1d1d1 1px,transparent 0),
|
||||
fixed 10px 10px /20px 20px radial-gradient(#d1d1d1 1px,transparent 0)
|
||||
*/
|
||||
|
||||
/** Honeycomb pattern
|
||||
*
|
||||
background:
|
||||
radial-gradient(circle farthest-side at 0% 50%,#fb1 23.5%,rgba(240,166,17,0) 0)21px 30px,
|
||||
radial-gradient(circle farthest-side at 0% 50%,#B71 24%,rgba(240,166,17,0) 0)19px 30px,
|
||||
linear-gradient(#fb1 14%,rgba(240,166,17,0) 0, rgba(240,166,17,0) 85%,#fb1 0)0 0,
|
||||
linear-gradient(150deg,#fb1 24%,#B71 0,#B71 26%,rgba(240,166,17,0) 0,rgba(240,166,17,0) 74%,#B71 0,#B71 76%,#fb1 0)0 0,
|
||||
linear-gradient(30deg,#fb1 24%,#B71 0,#B71 26%,rgba(240,166,17,0) 0,rgba(240,166,17,0) 74%,#B71 0,#B71 76%,#fb1 0)0 0,
|
||||
linear-gradient(90deg,#B71 2%,#fb1 0,#fb1 98%,#B71 0%)0 0 #fb1;
|
||||
background-size: 40px 60px;
|
||||
*/
|
||||
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
*/
|
103
project-page/styles/index.module.css
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
.sectionSkewed {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: 100% 0;
|
||||
transform: skewY(-2deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sectionSkewedContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.leftLights::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 1223px;
|
||||
height: 912px;
|
||||
|
||||
width: 1200px;
|
||||
height: 912px;
|
||||
|
||||
left: calc(50% - 1100px);
|
||||
top: -10%;
|
||||
|
||||
background: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
rgba(255, 214, 0, 0.2) 0%,
|
||||
rgba(255, 168, 0, 0) 100%
|
||||
);
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.lightsTwo::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 1223px;
|
||||
height: 912px;
|
||||
|
||||
width: 1200px;
|
||||
height: 912px;
|
||||
|
||||
left: calc(50%);
|
||||
top: 0px;
|
||||
|
||||
background: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
rgba(255, 214, 0, 0.2) 0%,
|
||||
rgba(255, 168, 0, 0) 100%
|
||||
);
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
.leftLights::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 25%;
|
||||
height: 900px;
|
||||
left: -12.5%;
|
||||
top: calc(50% - 900px / 2 + 151px);
|
||||
opacity: 0.2;
|
||||
background: linear-gradient(180deg, #77b8ff 0%, rgba(42, 138, 246, 0.4) 100%);
|
||||
filter: blur(125px);
|
||||
transform: rotate(-15deg);
|
||||
border-bottom-left-radius: 25% 25%;
|
||||
border-bottom-right-radius: 25% 25%;
|
||||
border-top-left-radius: 100% 100%;
|
||||
border-top-right-radius: 100% 100%;
|
||||
z-index: 200;
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.leftLights::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 40%;
|
||||
height: 422px;
|
||||
left: 0px;
|
||||
top: calc(50% - 422px / 2 + 298px);
|
||||
opacity: 0.5;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(29, 92, 162, 0.2) 0%,
|
||||
rgba(42, 138, 246, 0.4) 100%
|
||||
);
|
||||
filter: blur(125px);
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
*/
|
16
project-page/tailwind.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter']
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
2104
project-page/yarn.lock
Normal file
@ -17,7 +17,7 @@ If you would like to make your first contribution, here is a handy checklist we
|
||||
|
||||
|
||||
## Quick overview
|
||||
Wasp compiler is implemented in Haskell, but you will also see a lot of Javascript and other web technologies because Wasp compiler transpiles Wasp code into them.
|
||||
Wasp compiler is implemented in Haskell, but you will also see a lot of Javascript and other web technologies because Wasp compiles it's own code into them.
|
||||
|
||||
You don't have to be expert in Haskell to contribute or understand the code, since we don't use complicated Haskell features much -> most of the code is relatively simple and straight-forward, and we are happy to help with the part that is not.
|
||||
|
||||
|
1
web/.gitignore
vendored
@ -18,3 +18,4 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.vercel
|
||||
|
@ -212,6 +212,12 @@ and if you assign yourself a `Guest` role on the Discord server and then type `!
|
||||
|
||||
### Deploying the bot
|
||||
|
||||
:::note
|
||||
Heroku used to offer free apps under certain limits. However, as of November 28, 2022, they ended support for their free tier. https://blog.heroku.com/next-chapter
|
||||
|
||||
As such, we have updated our Deployment docs with new recommendations: https://wasp-lang.dev/docs/deploying
|
||||
:::
|
||||
|
||||
While there are many ways to deploy the Discord bot, I will shortly describe how we did it via Heroku.
|
||||
|
||||
We created a Heroku app `wasp-discord-bot` and set up the "Automatic deploys" feature on Heroku to automatically deploy every push to the `production` branch (our bot is on Github).
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Feature Release Announcement - Wasp Jobs
|
||||
title: Feature Announcement - Wasp Jobs
|
||||
authors: [shayneczyzewski]
|
||||
image: /img/jobs-snippet2.png
|
||||
tags: [webdev, wasp, feature, jobs]
|
||||
|
@ -18,6 +18,12 @@ This is on our take on the situation and what we think things might look like in
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
:::danger Trending post!
|
||||
|
||||
This post was trending on HackerNews - you can see the discussion [here](https://news.ycombinator.com/item?id=32098144).
|
||||
|
||||
:::
|
||||
|
||||
## Why (ML) code generation?
|
||||
|
||||
In order to make development faster, we came up with IDE autocompletion - e.g. if you are using React and start typing `componentDid`, IDE will automatically offer to complete it to `componentDidMount()` or `componentDidLoad()`. Besides saving keystrokes, maybe even more valuable is being able to see what methods/properties are available to us within a current scope. IDE being aware of the project structure and code hierarchy also makes refactoring much easier.
|
||||
|
@ -1,7 +1,6 @@
|
||||
---
|
||||
title: How to get started with Haskell in 2022 (the straightforward way)
|
||||
authors: [martinsos]
|
||||
image: /img/filip-headshot-min.jpeg
|
||||
tags: [webdev, haskell, language]
|
||||
---
|
||||
|
||||
@ -12,11 +11,11 @@ import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
Haskell is a unique and beautiful language that is worth learning, if for nothing else, then just for the concepts it introduces. They will expand your view on programming.
|
||||
Haskell is a unique and beautiful language that is worth learning, if for nothing else, then just for the concepts it introduces and their potential to expand your view on programming.
|
||||
|
||||
I have been programming in Haskell on and off since 2011 and professionally for the past 2 years, building a [compiler](https://github.com/wasp-lang/wasp). While in that time Haskell has become much more beginner-friendly, I keep seeing beginners who are overwhelmed by numerous popular options for build tools, installers, introductory educational resources, and similar. [Haskell’s homepage](https://www.haskell.org/) getting a call from the previous decade to give them their UX back also doesn’t help.
|
||||
I have been programming in Haskell on and off since 2011 and professionally for the past 2 years, building a [compiler](https://github.com/wasp-lang/wasp). While in that time Haskell has become much more beginner-friendly, I keep seeing beginners who are overwhelmed by numerous popular options for build tools, installers, introductory educational resources, and similar. [Haskell’s homepage](https://www.haskell.org/) getting a call from the previous decade to give them their UX back :D also doesn’t help!
|
||||
|
||||
That is why I decided to write this opinionated and practical post that will tell you exactly **how to get started with Haskell in 2022 in the most standard / common way.** Instead of worrying about decisions that you are not equipped to make at the moment (like “what is the best build tool Ifd??”), you can focus on enjoying learning Haskell :)!
|
||||
That is why I decided to write this opinionated and practical post that will tell you exactly how to get started with Haskell in 2022 in the most standard / common way. Instead of worrying about decisions that you are not equipped to make at the moment (e.g. “what is the best build tool?”), you can focus on enjoying learning Haskell :)!
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
@ -76,13 +75,15 @@ If you don't like LYAH, consider other popular books for beginners (none of them
|
||||
3. [Programming in Haskell](https://www.amazon.com/Programming-Haskell-Graham-Hutton/dp/1316626229)
|
||||
|
||||
Whatever book you go with, don’t get stuck for too long on concepts that are confusing to you, especially towards the end of the book. Some concepts will just need time to click; don’t expect to grasp it all on the first try. Whatever you do grasp from the first read will likely be more than enough to get going with your first projects in Haskell. You can always come back to those complex concepts later and understand them better. Also, don’t be shy to ask the community -> there are many Haskellers out there happy to support you in your learning!
|
||||
NOTE: When I say "don't get stuck", I don't mean you should skip the difficult concept after first hurdle. No, you should spend some hours experimenting, looking at it from different angles, playing with it, trying to crack it. But you shouldn't spend days trying to understand the same concept (e.g. function as a monad) and then feel defeated due to not grasping it 100%. Instead, if you put proper effort but stuff is not completely clicking, tap yourself on the back and move on for now.
|
||||
:::note
|
||||
When I say "don't get stuck", I don't mean you should skip the difficult concept after the first hurdle. No, you should spend some hours experimenting, looking at it from different angles, playing with it, trying to crack it. But you shouldn't spend days trying to understand the same concept (e.g. function as a monad) and then feel defeated due to not grasping it 100%. Instead, if you put proper effort but stuff is not completely clicking, tap yourself on the back and move on for now.
|
||||
:::
|
||||
|
||||
Once you take the first pass through the book, I recommend doing a project or two. You can come up with an idea yourself, or you can follow one of the books that guide you through it.
|
||||
|
||||
For example:
|
||||
|
||||
1. [Learn Haskell by building a blog generator](https://lhbg-book.link/) -> free, starts from 0 knowledge, and could even be used as the very first resource
|
||||
1. [Learn Haskell by building a blog generator](https://lhbg-book.link/) -> free, starts from 0 knowledge, and could even be used as the very first resource, instead of e.g. LYAH.
|
||||
2. [The Simple Haskell Handbook](https://marcosampellegrini.com/simple-haskell-book) -> not free, expects you to know the basics of Haskell already
|
||||
|
||||
Once you have more experience with projects, I would recommend re-reading your beginner book of choice. This time, you can skip the parts you already know and focus on what was confusing before. You will likely have a much easier time grasping those harder concepts.
|
||||
|
@ -12,6 +12,7 @@ We’ll build a web app to solve every developer's most common problem – findi
|
||||
|
||||
Best excuse of all time! [Taken from here.](https://xkcd.com/303/)
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## The requirements were unclear.
|
||||
|
||||
@ -153,14 +154,8 @@ export const saveExcuse = async (excuse, context) => {
|
||||
import axios from 'axios';
|
||||
|
||||
export const getExcuse = async () => {
|
||||
return axios
|
||||
.get('https://api.devexcus.es/')
|
||||
.then(res => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
const response = await axios.get('https://api.devexcus.es/')
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getAllSavedExcuses = async (_args, context) => {
|
||||
|
163
web/blog/2022-09-29-journey-to-1000-gh-stars.md
Normal file
@ -0,0 +1,163 @@
|
||||
---
|
||||
title: How Wasp reached 1,000 stars on GitHub (detailed stats & timeline)
|
||||
authors: [matijasos]
|
||||
image: /img/1000-gh-stars/1k_gh_stars_chart.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
Wasp is an open-source configuration language for building full-stack web apps that integrates with React & Node.js. We launched first prototype 2 years ago, currently are at 1.9k stars on GitHub and will be releasing Beta in the coming months.
|
||||
|
||||
It was very hard for us to find and be able to learn from early inception stories of successful OSS projects and that's why we want to share what it looked like for Wasp.
|
||||
|
||||
|
||||
![1k stars chart](../static/img/1000-gh-stars/1k_gh_stars_chart.png)
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Before the stars: Is this really a problem? (1 year)
|
||||
|
||||
My co-founder and twin brother Martin and I got an initial idea for Wasp in 2018, while developing a web platform for running bioinformatics analysis in the cloud for one London-based startup.
|
||||
|
||||
It was our third or fourth time creating a full-stack app from scratch with the latest & hottest stack. This time, it was React/Node.js; for our previous projects, we went through PHP/Java/Node.js on the back-end and jQuery/Backbone/Angular on the front-end. Because Martin and I felt we were spending a lot of time relearning how to use the latest stack just to build the same features all over again (auth, CRUD, forms, async jobs, etc.), we asked ourselves: *Why not abstract these common functionalities in a stack-agnostic, higher-level language (like e.g. SQL does for databases) to never reimplement them again?*
|
||||
|
||||
Before we jumped into coding, we wanted to make sure this is a problem that actually exists and that we understand it well (enough). In our previous startup we found Customer Development (aka talking to users) extremely helpful, so we decided to do it again for Wasp.
|
||||
|
||||
In a month or so we conducted 25 problem interviews, probing around “What is your biggest challenge with web app development?” After we compiled the results, we identified the following four problems as the most significant ones and decided to focus on them in our v1:
|
||||
|
||||
- It is hard to **quickly start** a new web app and make sure **the best practices are being followed**.
|
||||
- There is a lot of duplication/boilerplate in **managing the state** across front-end, back-end, and the database.
|
||||
- A lot of **common features are re-implemented** for every new app.
|
||||
- Developers are overwhelmed by the **increasing tool complexity** and don't want to be responsible for managing it.
|
||||
|
||||
We also clustered the answers we got by topics, so we could dive deeper and identify the areas that got the most attention:
|
||||
<ImgWithCaption
|
||||
alt="Start and setup of a web app - problems"
|
||||
source="img/1000-gh-stars/wasp-cust-dev-start-and-setup.png"
|
||||
caption="Interviewee problems regarding starting and setting up a new web app."
|
||||
/>
|
||||
The reason why we stopped at 25 was that the answers started repeating themselves. We felt that we identified the initial patterns and were ready to move on.
|
||||
|
||||
## 0-180 ⭐️: First Contact (7 months)
|
||||
After confirming and clarifying the problem with other developers, Martin and I felt we finally deserved to do some coding. (Ok, I admit, we had actually already started, but the interviews made us feel better about it 😀). We created a new repo on GitHub and started setting up the tooling & playing around with the concept.
|
||||
|
||||
For the next couple of months, we treated Wasp as a side project/experiment and didn’t do any marketing. However, we were well aware of how crucial external feedback is. So, once we built a very rudimentary code generation functionality, we also created a project page that we could share with others to explain what we’re working on and ask for feedback.
|
||||
|
||||
At that point, we came up with the first “real” name for Wasp - **STIC: Specification To Implementation Compiler**, as the big vision for Wasp was to be a stack-agnostic, specification language from which we could generate the actual code in e.g. React & Node.js or even some other stack.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="STIC - first project page"
|
||||
source="img/1000-gh-stars/stic-project-page.png"
|
||||
caption="Our first page for Wasp! Not the best at explaining what Wasp does, though."
|
||||
/>
|
||||
|
||||
### Baby steps on Reddit and Hacker News
|
||||
|
||||
Our preferred way of distributing STIC project page was through relevant subreddits - r/webdev, r/coding, r/javascript, r/Haskell, r/ProgrammingLanguages, ….
|
||||
|
||||
[This](https://www.reddit.com/r/javascript/comments/f38h1t/askjs_we_are_developing_a_declarative_dsl_for/) was the first Reddit post we’ve ever made about Wasp:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="First Wasp post on Reddit"
|
||||
source="img/1000-gh-stars/wasp-reddit-first-post.png"
|
||||
caption="Our first Reddit post! We managed to get some feedback before we got banned."
|
||||
/>
|
||||
|
||||
One important thing we learned is that Reddit doesn’t like self-promotion. Sometimes, even if you’re only asking for feedback, the mods (and bots) will see it as self-promo and ban your post. It depends a lot on the mods, though. Reaching out to them and asking for explanation sometimes helps, but not very often. All subreddits have their own rules and guidelines that describe when or how it is OK to post about your project (e.g., /r/webdev has “Showoff Saturdays”), and we tried to follow them as best as we could.
|
||||
|
||||
After Reddit, we also launched on HN. This was our first ever launch there! We scored 20 points and received a few motivating comments:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="First Wasp post on Reddit"
|
||||
source="img/1000-gh-stars/first-hn-launch.png"
|
||||
/>
|
||||
|
||||
### Listening to users
|
||||
Martin and I also followed up with the people we had previously interviewed about their problems in web dev. We showed them STIC project page and asked for comments. From all the feedback we captured, we identified the following issues:
|
||||
|
||||
- **Developers were not familiar with a term “DSL.”** Almost all of us use a DSL on a daily basis (e.g., SQL, HCL (Terraform), HTML), but it’s not a popular term.
|
||||
- **Developers feared learning a new programming language**. Although our goal was never to replace e.g. Java or Typescript but to make Wasp work alongside it, we discovered that we had failed to communicate it well. Our messaging made developers feel they have to drop all their previous knowledge and start from scratch if they want to use Wasp.
|
||||
- **Nobody could try Wasp yet + there wasn’t any documentation besides the project page**. Our code was public, but we didn’t have a build/distribution system yet. Only a devoted Haskell developer could build it from the source. This made it hard for developers to buy into the high-level vision, as there was nothing they could hold onto. Web frameworks/languages are very “tactile” — it’s hard to judge one without trying it out.
|
||||
|
||||
## 180-300 ⭐️ : Anybody can try Wasp out + Docs = Alpha! (3 months)
|
||||
After processing this feedback, we realized that the next step for us was to get Wasp into the condition where developers can easily try it out without needing any extra knowledge or facing the trouble of compiling from the source. That meant polishing things a bit, adding a few crucial features, and writing our first documentation, so that users would know how to use it.
|
||||
|
||||
To write our docs, we picked [Docusaurus](https://docusaurus.io/) — an OSS writing platform made by Facebook. We saw several other OSS projects using it for their docs + its ability to import React in your markdown was amazing. Docusaurus gave us a lot of initial structure, design and features (e.g., search), saving us from reinventing the wheel.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="First Wasp docs"
|
||||
source="img/1000-gh-stars/first-docs.png"
|
||||
caption="Martin made sure to add a huge Alpha warning sign :D"
|
||||
/>
|
||||
|
||||
Our M.O. at the time was to focus pretty much exclusively on one thing, either development or community. Since Wasp team consisted of only Martin and me, it was really hard to do multiple things at once. After the docs were out and Wasp was ready to be easily downloaded, we called this version “Alpha” and switched once again into the “community” mode.
|
||||
|
||||
## 300-570 ⭐️ : Big break on Reddit and Product Hunt (2 months)
|
||||
|
||||
Once Alpha was out, we [launched again on HackerNews](https://news.ycombinator.com/submitted?id=matijash) and drew a bit of attention (34 upvotes and 3 comments). However, that was little compared to our Reddit launches, where we scored 263 upvotes on [r/javascript](https://www.reddit.com/r/javascript/comments/jvv1yg/together_with_my_brother_ive_been_working_on_wasp/) and 365 upvotes on [r/reactjs](https://www.reddit.com/r/reactjs/comments/jx5fyg/together_with_my_brother_ive_been_working_on_wasp/):
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Big break on Reddit"
|
||||
source="img/1000-gh-stars/reddit-big-break.png"
|
||||
caption="They love me! [insert Tobey Maguire as Spiderman]"
|
||||
/>
|
||||
|
||||
Compared to the volume of attention and feedback we’ve been previously receiving, this was a big surprise for us! Here are some of the changes in messaging that we made for the Reddit launches:
|
||||
|
||||
- **Put prefix “declarative” in front of the “language”** to convey that it’s not a regular programming language like Python or Javascript but rather something much more lightweight and specialized.
|
||||
- **Emphasized that Wasp is not a standalone language that will replace your current stack** but rather a “glue” between your React & Node.js code, allowing you to keep using your favourite stack.
|
||||
- **Focused on the benefits like “less boilerplate,”** which is a well known pain in web development.
|
||||
|
||||
:::tip Docs made the difference
|
||||
|
||||
Once we added the docs, we noticed a peculiar thing: **developers became much less trigger-happy to criticize the project, especially in a non-constructive way**. Our feeling was the majority of developers who were checking Wasp out still didn’t read the docs in detail (or at all), but the sheer existence of them made them feel there is more content they should go through before passing the final judgment.
|
||||
|
||||
:::
|
||||
|
||||
### Winning #1 Product of The Day on Product Hunt
|
||||
|
||||
After HN and Reddit, we continued with the “Alpha launch” mindset and set ourselves to launch Wasp on Product Hunt. It was our first time ever launching on PH, so we didn’t know what to expect. We googled some advice, did maybe a week of preparation (i.e., wrote the copy, asked a few friends to share their experiences with Wasp once we’re live), and that was it.
|
||||
|
||||
We launched [Wasp on PH on Dec 6, 2020](https://www.producthunt.com/products/wasp-lang-alpha#wasp-lang-alpha) and it ended up as Product of the day! That gave us a boost in stars and overall traction. Another benefit of PH was that Wasp also ended up in their daily newsletter, which supposedly has over a million subscribers. All this gave us quite a boost and visibility increase.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Product Hunt launch"
|
||||
source="img/1000-gh-stars/ph-launch.png"
|
||||
/>
|
||||
|
||||
## 570-1000 ⭐️ : Wasp joins YC + “Official” HN launch (2.5 months)
|
||||
|
||||
Soon after Product Hunt, Wasp joined Y Combinator for their W21 batch. We had applied two times before and always made it to the interviews, but did not get in. This time, the traction tipped the scales in our favour. (You can read more about our journey to YC [here](https://wasp-lang.dev/blog/2021/02/23/journey-to-ycombinator).)
|
||||
|
||||
For the first month of YC, there was a lot of admin and setup work to deal with alongside the regular program. That added a third dimension to our existing two areas of effort. Once we went past that, we could again put more focus on product and community development.
|
||||
|
||||
Our next milestone was to launch Wasp on Hacker News, but this time “officially” as a YC-backed company. **Hacker News provides a lot of [good tips](https://news.ycombinator.com/yli.html) on how to successfully launch and 80% of the advice applies even if your product isn’t backed by YC**. I wish I had known about it before. The gist of the advice is to write in a clear and succinct way and to avoid buzzwords, superlatives, and salesy tone above all. Consider HN readers as your peers and explain what you do in a way you would talk to a friend over a drink. It really works that way.
|
||||
|
||||
We went through the several iterations of the text, sweated over how it’s gonna go, and when the day finally came — we launched! It went beyond all our expectations. With 222 points and 79 comments, **our HN launch was one of the most successful launches (#9) out of 300+ companies in the W21 batch.** Many developers and VCs that checked our launch afterwards were surprised how much positive feedback Wasp received, especially given how honest and direct HN audience can be.
|
||||
|
||||
**HN launch brought us about 200 stars right away**, and the rest came in the following weeks. As it was February and the YC program was nearing its end, we needed to shift gears again and focus on fundraising. This put all the other efforts on the back burner. (You can read about our fundraising learnings from 250+ meetings in 98 days [here](https://wasp-lang.dev/blog/2021/11/22/fundraising-learnings).) But the interest of the community remained and even without much activity from our side they kept coming and trying Wasp out.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="YC HN launch"
|
||||
source="img/1000-gh-stars/yc-hn-launch.png"
|
||||
/>
|
||||
|
||||
## Conclusion: understanding users > number of stars
|
||||
|
||||
Our primary goal was never to reach X stars, but rather to understand how we can make Wasp more helpful so that developers would want to use it for their projects. As you could read above, even well before we started a repository we made sure to talk to developers and learn about their problems.
|
||||
|
||||
We also kept continually improving how we present Wasp - had we not pivoted our message from *“Wasp is a new programming language”* to *“Wasp is a simple config language that works alongside React & Node.js”* we wouldn’t have been where we are today.
|
||||
|
||||
On the other hand, stars have become an unofficial “currency” of GitHub and developers and VCs alike consider it when evaluating a project. They shouldn’t be disregarded and you should make it easy for users who like your product to express their support by starring your repo (like I’m doing right [here](https://github.com/wasp-lang/wasp)), but that should always be a second order of concern.
|
||||
|
||||
## Good luck!
|
||||
|
||||
I hope you found this helpful and that we shed some light on how things can look like in the early stages of an OSS project. Also, keep in mind this was our singular experience and that every story is different, so take everything with a grain of salt and pick only what makes sense for you and your product.
|
||||
|
||||
We wish you the best of luck and feel free to reach out if you'll have any questions!
|
75
web/blog/2022-10-28-farnance-hackathon-winner.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: 'Farnance: How Julian built a SaaS for farmers with Wasp and won a hackathon!'
|
||||
authors: [matijasos]
|
||||
image: /img/farnance/farnance-hero-shot.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
![farnance hero shot](../static/img/farnance/farnance-hero-shot.png)
|
||||
|
||||
|
||||
[Julian LaNeve](https://jlaneve.github.io/) is an engineer and data scientist who currently works at [Astronomer.io](http://Astronomer.io) as a Product Manager. In his free time, he enjoys playing poker, chess and [winning](https://www.smudailycampus.com/news/smu-graduate-julian-laneve-wins-100k-grand-prize-from-data-science-competition) data science competitions.
|
||||
|
||||
His project, [Farnance](https://farnance.netlify.app/), is a SaaS marketplace that allows farmers to transform their production into a digital asset on blockchain. Julian and his team developed Farnance as a part of the London Business School’s annual hackathon [HackLBS 2021](https://hacklbs.devpost.com/), and ended up as winners among more than 250 participants competing for 6 prizes in total!
|
||||
|
||||
Read on to learn why Julian chose Wasp to develop and deploy Farnance and what parts he enjoyed the most.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Finding a perfect React & Node.js hackathon setup
|
||||
|
||||
Julian had previous experiences with React and Node.js and loved that he could use JavaScript across the stack, but setting up a new project and making sure it uses all the latest packages (and then also figuring out how to deploy it) was always a pain. Since the hackathon only lasted for two days, he needed a quick way to get started but still have the freedom to use his favourite stack.
|
||||
|
||||
## The power of one-line auth and No-API approach
|
||||
|
||||
Julian first learned about Wasp when it [launched on HN](https://news.ycombinator.com/item?id=26091956) and decided it would be a perfect tool for his case. The whole app setup, across the full stack, is covered out-of-the-box, simply by typing `wasp new farnance`, and he is ready to start writing own React & Node.js code.
|
||||
|
||||
Except on the app setup, the team saved a ton of time by not needing to implement the authentication and a typical CRUD API, since it is covered by Wasp as well. They could also deploy everything for free on Heroku and Netlify in just a few steps, which was a perfect fit for a hackathon.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Julian's testimonial on Discord"
|
||||
source="img/farnance/julian-discord-testimonial.png"
|
||||
/>
|
||||
|
||||
Farnance is still running and you can [try it out here](https://farnance.netlify.app/)! The source code is also [publicly available](https://github.com/jlaneve/Farnance), although note it is running on older version of Wasp so some things are a bit different.
|
||||
|
||||
## Spend more time developing features and less time reinventing the wheel
|
||||
|
||||
Julian was amazed by how fast he was able to get Farnance of the ground and share a working web app with the users! He decided to go with Google's material-ui for an UI framework which gave his app an instant professional look, although they didn’t have a dedicated designer on the team.
|
||||
|
||||
With all the common web app features (setup, auth, CRUD API) being taken care of by Wasp out-of-the-box they could invest all the time saved in developing and refining their unique features which in the end brought them victory!
|
||||
|
||||
> I’ve done plenty of hackathons before where I’ve built small SaaS apps, and there’s just so much time wasted setting up common utilities - stuff like user management, databases, routing, etc. Wasp handled all that for me and let me build out our web app in record time
|
||||
>
|
||||
> — Julian LaNeve - Farnance
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Farnance's dashboard"
|
||||
source="img/farnance/farnance-dashboard.png"
|
||||
caption="Farnance dashboard in action!"
|
||||
/>
|
||||
|
||||
|
||||
## Start quickly, but also scale without worries
|
||||
|
||||
:::note
|
||||
Heroku used to offer free apps under certain limits. However, as of November 28, 2022, they ended support for their free tier. https://blog.heroku.com/next-chapter
|
||||
|
||||
As such, we have updated our Deployment docs with new recommendations: https://wasp-lang.dev/docs/deploying
|
||||
:::
|
||||
|
||||
Since Wasp compiler generates a full-stack React & Node.js app under the hood, there aren’t any technical limitations to scaling Julian’s app as it grows and gets more users in the future. By running `wasp build` inside a project folder, developers gets both frontend files and a Dockerfile for the backend, which can then be deployed as any regular web app to the platform of your choice.
|
||||
|
||||
Wasp provides [step-by step instructions](/docs/deploying) on how to do it with Netlify and Fly.io for free, but we plan to add even more examples and more integrated deployment experience in the coming releases!
|
||||
|
||||
> Deploying the wasp app was incredibly easy - I didn’t have time to stand up full infrastructure in the 2 day hackathon and don’t have an infra/devops background, but I had something running on Netlify within an hour. Other projects at the hackathon struggled to do this, and putting access in the hands of the judges certainly helped get us 1st place.
|
||||
>
|
||||
> — Julian LaNeve - Farnance
|
116
web/blog/2022-11-15-auth-feature-announcement.md
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Feature Announcement - New auth method (Google)
|
||||
authors: [shayneczyzewski]
|
||||
image: /img/auth-hero.png
|
||||
tags: [webdev, wasp, feature, auth]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
<p align="center">
|
||||
<img alt="No login for you!"
|
||||
src={useBaseUrl('img/auth-hero.png')}
|
||||
width="300px"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
<WaspIntro />
|
||||
<InBlogCta />
|
||||
|
||||
## Prologue
|
||||
|
||||
We've all been there. Your app needs to support user authentication with social login, and you must now decide what to do next. Should you eschew the collective experience and wisdom of the crowd and YOLO it by rolling your own, praying you don't get pwned in prod? "Nah, I just ate some week-old sushi and can't take another risk that big anytime soon.", you rightly think.
|
||||
|
||||
Ok, surely you can just use a library, right? Open source software, baby! "Hmm, seems Library X, Y, and Z are all somewhat used, each with their pros/cons, nuances, and integration pain points. Oh wait, there are tutorials for each... but each says how hard they are to correctly set up and use. I scoped this feature for one day, not a one-week hair-pulling adventure (Dang scrum! Who likes it anyways? Oh yeah, PMs do. Dang PMs!)." Ok, something else. You need to brainstorm. `You instead start to surf Twitter and see an ad for some unicorn auth startup.`
|
||||
|
||||
Eureka, you can go with a third-party SaaS offering! "We shouldn't have to pay for a while (I ~~think?~~ hope!), and it's just another dependency, no biggie... #microservices, right?" "But what about outages, data privacy, mapping users between systems, and all that implicit trust you are placing in them?" you think. "What happens when Elon buys them next?" You gasp as if you walked by a Patagonia vest covered in that hot new *Burnt Hair* cologne.
|
||||
|
||||
"All I want is username and password auth with Google login support, why is that so hard in 2022?!? I miss Basic HTTP auth headers. I think I'll move off the grid and become a woodworker."
|
||||
|
||||
## Easy auth setup in Wasp
|
||||
|
||||
Wasp helps that dev by taking care of the entire auth setup process out of the box. Adding support for username and password auth, plus Google login, is super quick and easy for Wasp apps. We think this makes adding auth fast and convenient, with no external dependencies or frustrating manual configuration. Here’s how it works:
|
||||
|
||||
### Step 1 - Add the appropriate models
|
||||
|
||||
We need to store user info and the external mapping association for social logins. Here is an example you can start from and add new fields to:
|
||||
|
||||
```sql title="./main.wasp"
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
externalAuthAssociations SocialLogin[]
|
||||
psl=}
|
||||
|
||||
entity SocialLogin {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
provider String
|
||||
providerId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
@@unique([provider, providerId, userId])
|
||||
psl=}
|
||||
```
|
||||
|
||||
### Step 2 - Update `app.auth` to use these items
|
||||
|
||||
```css title="./main.wasp"
|
||||
app authExample {
|
||||
// ...
|
||||
auth: {
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {}
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3 - Get Google credentials and add environment variables
|
||||
|
||||
Follow the Google setup guide [here](https://wasp-lang.dev/docs/integrations/google) and add the environment variables to your `.env.server` file.
|
||||
|
||||
### Step 4 - Make use of the Google login button in your `Login` page component
|
||||
|
||||
```jsx title="./src/client/auth/Login.js"
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { GoogleSignInButton } from '@wasp/auth/buttons/Google'
|
||||
import LoginForm from '@wasp/auth/forms/Login'
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<LoginForm/>
|
||||
</div>
|
||||
<div>
|
||||
I don't have an account yet (<Link to="/signup">go to signup</Link>).
|
||||
</div>
|
||||
<div>
|
||||
<GoogleSignInButton/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
```
|
||||
|
||||
### Step 5 - Run the app!
|
||||
|
||||
## Epilogue
|
||||
|
||||
No need to move off the grid out of frustration when adding authentication and social login to your web app. [Here](https://github.com/shayneczyzewski/authExample) is a complete, minimal example if you want to jump right in, and [here](https://wasp-lang.dev/docs/language/features#authentication--authorization) are the full docs for more info. With just a few simple steps above, we've added authentication with best practices baked into our app so we can move on to solving problems that add value to our users!
|
170
web/blog/2022-11-16-alpha-testing-program-post-mortem.md
Normal file
@ -0,0 +1,170 @@
|
||||
---
|
||||
title: 'Alpha Testing Program: post-mortem'
|
||||
authors: [matijasos]
|
||||
image: /img/atp/welcome-to-atp-notion.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
We are working on a new web framework that integrates with React & Node.js, and also happens to be a language. As you can probably imagine, it’s not easy to get people to use a new piece of technology, especially while still in Alpha. On the other hand, without users and their feedback, it’s impossible to know what to build.
|
||||
|
||||
That is why we ran Alpha Testing Program for Wasp - here is what we learned and what went both well and wrong along the way.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="twitter DM - shared atp in swag groups"
|
||||
source="img/atp/swag-groups-twitter.png"
|
||||
/>
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## “Of course I know about Wasp! I just haven’t come around to trying it out yet.”
|
||||
|
||||
Although we hit the front page of HN [several](https://news.ycombinator.com/item?id=26091956) [times](https://news.ycombinator.com/item?id=32098144) and are about to reach 2,000 stars on GitHub, there is still a big difference between a person starring a repo and actually sitting down and building something with it.
|
||||
|
||||
Talking to people, we realised a lot of them had heard of Wasp, thought it was a neat idea, but hadn’t tried it out. These were the main reasons:
|
||||
|
||||
- having to find 30 mins to go through our **Build a Todo App** tutorial - *“I'm busy now, but I’ll do it next week.”*
|
||||
- building a bare-bones todo app is not that exciting
|
||||
- not having an idea what else to build
|
||||
- *“the product is still in alpha, so I will bookmark it for later”*
|
||||
|
||||
These are all obvious and understandable reasons. I must admit, I’m much the same — maybe even worse — when it comes to trying out something new/unproven. It just isn’t a priority, and without a push that will help me overcome all these objections, I usually don’t have an incentive to go through with it.
|
||||
|
||||
Having realised all that, we understood we needed to give people a reason to try Wasp out **now**, because that’s when we needed the feedback, not next week.
|
||||
|
||||
## Welcome to Wasp Alpha Testing Program!
|
||||
|
||||
<p align="center">
|
||||
<figure>
|
||||
<img alt="The team"
|
||||
src={useBaseUrl('img/atp/welcome-to-atp-notion.png')}
|
||||
/>
|
||||
<figcaption style={{color: '#808080'}}>I was having a bit too much fun <Link to={useBaseUrl('https://wasp-lang.notion.site/CLOSED-Welcome-to-Wasp-Alpha-Testing-program-f3a8a350802341abac87fb7831bb1e60')}>here</Link>, but Portal fans will understand.</figcaption>
|
||||
</figure>
|
||||
</p>
|
||||
|
||||
We quickly put together an admissions page for alpha testers in Notion (you can see it [here](https://wasp-lang.notion.site/Wasp-Alpha-Testing-Program-Admissions-dca25649d63849cb8dfc55881e4f6f82)) and started sharing it around. To counter the hurdles we mentioned above, we time-boxed the program (*”this is happening now and you have 48 hours to finish once you start*”) and promised a t-shirt to everyone that goes through the tutorial and fills out the feedback form.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Apply to ATP - CTA"
|
||||
source="img/atp/atp-apply-here.png"
|
||||
caption="CTA from the admissions page"
|
||||
/>
|
||||
|
||||
Soon, the first applications started trickling in! For each new applicant, we’d follow up with the [instructions](https://www.notion.so/CLOSED-Welcome-to-Wasp-Alpha-Testing-program-f3a8a350802341abac87fb7831bb1e60) on how to successfully go through the Alpha Testing Program:
|
||||
|
||||
- fill out intro form (years of experience, preferred stack, etc)
|
||||
- go through our “build a Todo app” tutorial
|
||||
- fill out the feedback form - what was good, what was bad etc.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Timeboxing"
|
||||
source="img/atp/timeboxing.png"
|
||||
caption="People were really respectful of this deadline and would politely ask to extend it in case they couldn’t make it."
|
||||
/>
|
||||
|
||||
But, soon after I got the following message on Twitter:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="twitter DM - shared atp in swag groups"
|
||||
source="img/atp/swag-groups-twitter.png"
|
||||
/>
|
||||
|
||||
We got really scared that we would get a ton of folks putting in minimal effort while trying Wasp out just to get the free swag, leaving us empty-handed and having learned nothing! On the other hand, we didn’t have much choice since we didn’t define the “minimum required quality” of feedback in advance.
|
||||
|
||||
Luckily, it wasn’t the problem in the end, even the opposite -- we did get a surge of applications, but only a portion of them finished the program and the ones that did left really high-quality feedback!
|
||||
|
||||
## How it went - test profile & feedback
|
||||
|
||||
### Tester profile
|
||||
|
||||
We received 210 applications and 53 out of those completed the program — 25% completion rate.
|
||||
|
||||
We also surveyed applicants about their preferred stack, years of programming experience, etc:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Intro survey - tester profile"
|
||||
source="img/atp/atp-intro-survey-yoe.png"
|
||||
caption="Yep, we like puns."
|
||||
/>
|
||||
|
||||
### The feedback
|
||||
|
||||
The feedback form evaluated testers’ overall experience with Wasp. We asked them what they found to be the best and worst parts of working with Wasp, as well as about the next features they’d like to see.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Feedback survey - experience"
|
||||
source="img/atp/atp-feedback-survey-exp.png"
|
||||
/>
|
||||
|
||||
**The bad parts**
|
||||
|
||||
What our testers were missing the most was a full-blown IDE and TypeScript support. Both of these are coming in Beta but only JS was supported at the time. Plus, there were some installation problems with Windows (which is not fully supported yet — best to use it through WSL).
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Feedback survey - the bad parts"
|
||||
source="img/atp/atp-bad-parts.png"
|
||||
/>
|
||||
|
||||
We were already aware that TypeScript support is an important feature, but didn’t have an exact feeling of how much - the feedback was really helpful and helped us prioritise our Beta backlog.
|
||||
|
||||
**The good parts**
|
||||
|
||||
Testers’ favourite part was the batteries-included experience, particularly the [auth model](/docs/tutorials/todo-app/06-auth).
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Feedback survey - the good parts"
|
||||
source="img/atp/atp-good-parts.png"
|
||||
/>
|
||||
|
||||
## Post-mortem: what didn’t go well
|
||||
|
||||
### No threshold for feedback quality
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Feedback quality"
|
||||
source="img/atp/atp-feedback-quality.png"
|
||||
/>
|
||||
|
||||
We didn’t put any kind of restrictions on the feedback form, e.g. minimal length of the feedback. That resulted in ~15%-20% of answers being single words, such as depicted above. I’m not sure if there is an efficient way to avoid this or just a stat to live with.
|
||||
|
||||
### Using free text form for collecting addresses
|
||||
|
||||
It never crossed our minds before that validating addresses could be such an important part of shipping swag, but turns out it is. It seems that there are a lot of ways to specify an address, some of which are different from what is expected by our post office, resulting in a number of shipments getting returned.
|
||||
|
||||
An ideal solution would be to use a specialized “address” field in a survey that would auto-validate it, but turns out Typeform (which we used) doesn’t have that feature implemented yet, although [it’s been highly requested](https://community.typeform.com/suggestions-feedback-34/address-field-question-type-2950).
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Shipment returned"
|
||||
source="img/atp/atp-shipment-returned.jpg"
|
||||
/>
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Shipment returned email"
|
||||
source="img/atp/atp-shipment-returned-email.png"
|
||||
/>
|
||||
|
||||
## The non-obvious benefit of Alpha Testing Program
|
||||
|
||||
What went well is that we got a lot of high-quality feedback that steered and fortified our plan for the upcoming Beta release.
|
||||
|
||||
The other big benefit is that we finally solved the *“looks cool but i’ll try it out later maybe”* problem. Overall, our usage went well up during the program, but even after it ended, the baseline increased significantly. This was the second-order effect we didn’t foresee.
|
||||
|
||||
Our understanding is that once people finally gave it a try, a portion of them felt the value first-hand and decided to keep using it for other projects as well.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Alpha testing program - usage spike"
|
||||
source="img/atp/atp-usage-spike.png"
|
||||
/>
|
||||
|
||||
## Summary & going forward: Beta
|
||||
|
||||
The overall conclusion from our Alpha Testing Program is it was a worthy effort which got us valuable feedback and positively affected the overall usage. Moving forward we’ll try to focus on ensuring more quality feedback and prioritising 1-to-1 communication to make sure we fully understand what bothers Wasp users and what we can improve. It also might be helpful to do testing in smaller batches so we are not overwhelmed with responses and can focus on the individual testers - that’s something we might try out in Beta.
|
||||
|
||||
As mentioned, the next stop is Beta! It comes out on the 27th of November - [sign up here](/#signup) to get notified.
|
90
web/blog/2022-11-16-tailwind-feature-announcement.md
Normal file
@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Feature Announcement - Tailwind CSS support
|
||||
authors: [shayneczyzewski]
|
||||
image: /img/tailwind-2.png
|
||||
tags: [webdev, wasp, feature, css]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
<p align="center">
|
||||
<img alt="Full stack devs"
|
||||
src={useBaseUrl('img/tailwind-1.png')}
|
||||
width="400px"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
<WaspIntro />
|
||||
<InBlogCta />
|
||||
|
||||
There are backend devs who can do some frontend, and frontend devs who can do some backend. But the mythical full stack dev is exceedingly rare (or more likely, a lie). Even as someone who falls into the meme category above, we *all* still need to make websites that **look noice**. This is a place where CSS frameworks can help.
|
||||
|
||||
But which one should you use? According to our *extensive research*, a statistically-questionable-but-you’re-still-significant-to-us 11 people on Twitter wanted us to add better support for [Tailwind](https://tailwindcss.com/). Which was lucky for us, since we already added it before asking them. 😅
|
||||
|
||||
<p align="center">
|
||||
<img alt="Twitter voting"
|
||||
src={useBaseUrl('img/tailwind-2.png')}
|
||||
width="400px"
|
||||
/>
|
||||
</p>
|
||||
|
||||
Ok, it wasn’t a huge stretch for us to do so preemptively. Tailwind is one of the most heavily used CSS frameworks out there today and seems to keep growing in popularity. So how do you integrate it into your Wasp apps? Like many things in Wasp, it’s really easy- just drop in two config files into the root of your project and you can then start using it! Here are the defaults:
|
||||
|
||||
```jsx title="./tailwind.config.js"
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
```
|
||||
|
||||
```jsx title="./postcss.config.js"
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
When these two files are present, Wasp will make sure all the required NPM dependencies get added, that [PostCSS](https://postcss.org/) plays nicely with Tailwind directives in CSS files, and that your JavaScript files are properly processed so you can use all the CSS selectors you want (provided you are properly equipped :D).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Best monitor"
|
||||
src={useBaseUrl('img/tailwind-3.png')}
|
||||
width="500px"
|
||||
/>
|
||||
</p>
|
||||
|
||||
With that in place, you can add the Tailwind directives to your CSS files like so:
|
||||
|
||||
```css title="./src/client/Main.css"
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* rest of content below */
|
||||
```
|
||||
|
||||
And then start using Tailwind classes in your components:
|
||||
|
||||
```jsx
|
||||
<h1 className="text-3xl font-bold underline">
|
||||
Hello world!
|
||||
</h1>
|
||||
```
|
||||
|
||||
As usual, Wasp will still automatically reload your code and refresh the browser on any changes. 🥳
|
||||
|
||||
Lastly, here is a small example that shows how to add a few Tailwind plugins for the adventurous ([wasp file](https://github.com/wasp-lang/wasp/blob/main/waspc/examples/todoApp/todoApp.wasp#L8-L9) and [Tailwind config](https://github.com/wasp-lang/wasp/blob/main/waspc/examples/todoApp/tailwind.config.js#L10-L11)), and [here](/docs/integrations/css-frameworks) are the docs for more details. We can’t wait to see what you make!
|
118
web/blog/2022-11-17-hacktoberfest-wrap-up.md
Normal file
@ -0,0 +1,118 @@
|
||||
---
|
||||
title: 'How Wasp reached all-time high PR count during Hacktoberfest: tips for OSS maintainers'
|
||||
authors: [maksym36ua]
|
||||
tags: [webdev, wasp, hacktoberfest, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
2078 lines of code across 24 PRs were changed in [Wasp repo](https://github.com/wasp-lang/wasp) during [HacktoberFest 2022](https://hacktoberfest.com/) - the most prominent online event for promoting and celebrating OSS culture. October has been a blast, to say the least, and the most active month in the repo's history.
|
||||
|
||||
This is the story of our journey along with the tips on leveraging Hacktoberfest to get your repo buzzing! 🐝🐝
|
||||
|
||||
## How it went: the stats
|
||||
|
||||
Let's take a quick look at the charts below (data obtained from [OSS Insight](https://ossinsight.io/analyze/wasp-lang/wasp) platform) 👇
|
||||
|
||||
<ImgWithCaption
|
||||
alt="PR history"
|
||||
source="img/hacktoberfest-wrap-up/pr-history.png"
|
||||
caption="24 contributor PRs in Oct, an all-time high!"
|
||||
/>
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Lines of code changes"
|
||||
source="img/hacktoberfest-wrap-up/code-lines-history.png"
|
||||
caption="On the other hand, number of changed LoC isn't that huge"
|
||||
/>
|
||||
|
||||
While the number of PRs is at an all-time high, the number of updated lines of code is fewer than usual. If we take a look at the distribution of PR sizes in the first chart, we can see that "xs" and "s" PRs are in the majority (20 out of 24).
|
||||
|
||||
**This brings us to our first conclusion: first-time contributors start with small steps!** The main benefit here is getting potential contributors interested and familiar with the project, rather than expecting them to jump in and
|
||||
immediately start implementing the next major feature. Efforts like that require investing time to understand and digest codebase architecture, design decisions and the development process.
|
||||
|
||||
On the other hand, being able to implement and merge any feature, no matter the size, from beginning to the end, and to get your name on the list of contributors of your favourite project is an amazing feeling! That will make your contributors feel like superheroes and motivate them to keep taking on larger and larger chunks, and maybe eventually even join the core team!
|
||||
|
||||
**Thus, the second conclusion would be: don’t underestimate the significance of small PRs!** It's not about reducing your backlog, but rather encouraging developers to get engaged with your project in a friendly way.
|
||||
|
||||
:::tip
|
||||
|
||||
To make it easier for your new contributors, you can prepare in advance good issues to get started with - e.g. smaller bugs, docs improvements, fun but isolated problems, etc.
|
||||
|
||||
We added [`good-first-issue`](https://github.com/wasp-lang/wasp/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label to such issues in Wasp repo, and even added extra context such as `no-haskell`, `webdev`, `example`, `docs`.
|
||||
|
||||
:::
|
||||
|
||||
With your repo being set, the next question is *"How do I get people to pick my project to work on"*? Relying solely
|
||||
on putting "Hacktoberfest" topic on your GitHub repo won't do the trick, not with thousands of other repos doing the same.
|
||||
|
||||
If you want to get noticed, **you need to do marketing. A lot of it. The name of the game here is what you put in is what you get back.** Let's talk about this in more detail.
|
||||
|
||||
## A thin line between genuine interactions and annoying self-promotion
|
||||
|
||||
First and foremost, you'll need to create [an entry point](https://github.com/wasp-lang/wasp/issues/735) with all the necessary information for the participants. We opted for a GitHub issue where we categorized Hacktoberfest issues by type, complexity, etc, but it can be anything - a dedicated landing page, Medium/Dev.to article, or whatever works for you. Once you have that, you can start promoting it.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Hacktoberfest entry point - gh issue"
|
||||
source="img/hacktoberfest-wrap-up/hf-gh-entry-point.png"
|
||||
caption="Our entry point for Hacktoberfest"
|
||||
/>
|
||||
|
||||
Our marketing strategy consisted of the following:
|
||||
|
||||
1. Tweeting regularly - what's new, interesting issues, ...
|
||||
|
||||
2. Writing meaningful Reddit posts about your achievements
|
||||
|
||||
3. Hanging out in HacktoberFest [Discord server](https://discord.com/invite/hacktoberfest), chatting with others and answering their questions
|
||||
|
||||
4. Checking posts with [appropriate](https://dev.to/t/hacktoberfest) [tags](https://medium.com/tag/hacktoberfest2022) on different blogging websites like Medium, Dev.to, Hashnode, etc. and participating in conversations.
|
||||
|
||||
There are plenty of other ways to advertise your project, like joining events or writing articles. Even [meme contests](https://github.com/dailydotdev/memetoberfest). The activities mentioned above worked the best for us. Let’s dive a bit deeper.
|
||||
|
||||
Tweets are pretty obvious - as mentioned, you can share updates on how stuff is going. Tag contributors, inform your followers about available issues and mention those who might be a good fit for tackling them.
|
||||
|
||||
Reddit is a much more complex beast. You need to avoid clickbait post titles, comply with subreddit rules on self-promotion and try to give meaningful info to the community simultaneously. Take less than you give, and you’re good.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="posting on reddit"
|
||||
source="img/hacktoberfest-wrap-up/oss-reddit-post.png"
|
||||
caption="How posting on Reddit feels"
|
||||
/>
|
||||
|
||||
|
||||
The Discord server marketing was pretty straightforward. There’s even a dedicated channel for self-promotion. In case you're not talkative much, dropping a link to your project is OK, and that’s it. On the other hand, the server is an excellent platform for discussing Hacktoberfest-related issues, approaches, and ideas. The more you chat, the higher your chances of drawing attention to your project.
|
||||
|
||||
The most engaging but also time consuming activity was commenting on blog posts of other Hacktoberfest participants. **Pretending that you’re interested in the topic only to leave a self-promoting comment will not bring you anywhere - it can only result in your comment being removed**. Make sure to provide value: add more information on the topic of the article, address specific points the author may have missed, or mention how you’ve dealt with the related issue in your project.
|
||||
|
||||
Be consistent and dedicate time to regularly to check new articles and jump into discussions. Share a link to your repo only if it fits into the flow of the conversation.
|
||||
|
||||
![Content marketing in a nutshell](../static/img/hacktoberfest-wrap-up/content-marketing-in-a-nutshell.png)
|
||||
|
||||
## Was it worth it?
|
||||
|
||||
Before joining HacktoberFest as maintainers, we weren’t sure it would be worth the time investment. Our skepticism was reinforced by the following:
|
||||
|
||||
1. [Mentions](https://www.reddit.com/r/developersIndia/comments/xvynx9/hacktoberfest_is_ruining_opensource/) of people submitting trivial PRs just to win the award
|
||||
|
||||
2. The fact that we're making a relatively complex project (DSL for developing React + Node.js full-stack web apps with less code) and it might be hard for people to get into it
|
||||
|
||||
3. The compiler is written is Haskell, with templates in JavaScript - again, not the very common project setup
|
||||
|
||||
Fortunately, none of this turned out to be a problem! We've got 24 valid PRs, both Haskell and non-Haskell, a ton of valuable feedback, and several dozen new users and community members.
|
||||
|
||||
## Wrap up
|
||||
|
||||
Don’t expect magic to happen. HacktoberFest is all about smaller changes and getting community introduced to your project. Be ready to promote your repo genuinely and don’t be afraid to take part in the contest. We hope that helps and wish you the best of luck!
|
||||
|
||||
Remember, HacktoberFest is all about the celebration of open source. Stick to that principle, and you’ll get the results you could only wish for!
|
||||
|
||||
## P.S. - Thanks to our contributors!
|
||||
|
||||
Massive shout out to our contributors: [@ussgarci](https://twitter.com/ussgarci), [@h4r1337](https://twitter.com/h4r1337), [@d0m96](https://twitter.com/d0m96), [@EmmanuelCoder](https://twitter.com/EmmanuelCoder), [@gautier_difolco](https://twitter.com/gautier_difolco), [@vaishnav_mk1](https://twitter.com/vaishnav_mk1), [@NeoLight1010](https://twitter.com/NeoLight1010), [@abscubix](https://twitter.com/abscubix), [@JFarayola](https://twitter.com/JFarayola), [@Shahx95](https://twitter.com/Shahx95) and everyone else for making it possible. You rock! 🤘
|
75
web/blog/2022-11-26-erlis-amicus-usecase.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: 'Amicus: See how Erlis built a SaaS for legal teams with Wasp and got first paying customers!'
|
||||
authors: [matijasos]
|
||||
image: /img/amicus-usecase/amicus-hero-shot.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
![amicus hero shot](../static/img/amicus-usecase/amicus-hero-shot.png)
|
||||
|
||||
|
||||
[Erlis Kllogjri](https://github.com/ErlisK) is an engineer based in San Francisco with broad experience ranging from mechanical engineering and C/C++ microcontroller programming to Python and web app development. In his free time, Erlis enjoys working on side projects, which is also how Amicus started out.
|
||||
|
||||
Amicus is a SaaS for legal teams - think about it as "Asana for lawyers", but with features and workflows tailored to the domain of law.
|
||||
|
||||
Read on to learn how long it took Erlis to develop the first version of his SaaS with Wasp, how he got his first paying customers, and what features he plans to add next!
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Looking for a full-stack “all-in-one” solution, with React & Node.js
|
||||
|
||||
Erlis first learned about Wasp on HackerNews and it immediately caught his attention, particularly the configuration language part. One of the companies he worked at in the past had its own internal DSL in the hardware domain, and he understood how helpful it could be for moving fast and avoiding boilerplate.
|
||||
|
||||
Erlis also had previous experience in web development, especially on the front-end side in React and Javascript, so that made Wasp a logical choice.
|
||||
|
||||
> I was looking at other solutions, but none of them were full-stack and sounded like a lot of work just to stitch everything together and get started. I just wanted to get the job done and didn’t care about picking the stack specifics myself. Wasp was really helpful as it set me up with the best practices and I had everything running in just a few minutes!
|
||||
>
|
||||
> — Erlis Kllogjri - Amicus
|
||||
|
||||
## Building Amicus v1.0 and getting first customers!
|
||||
|
||||
The idea for Amicus came from his brother, who is employed at a law firm - talking about their process and challenges in executing them, Erlis thought it would be an interesting side project, especially given there is a real problem to solve.
|
||||
|
||||
Soon, the first version of Amicus was live! It was made in a true lean startup fashion, starting with the essential features and immediately being tested with users.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Amicus's dashboard"
|
||||
source="img/amicus-usecase/amicus-dashboard.png"
|
||||
caption="Amicus's dashboard, using Material-UI"
|
||||
/>
|
||||
|
||||
Erlis used Material-UI as a UI library since it came with one of the example apps built in Wasp (Beta introduced Tailwind support!). Users could track their clients, active legal matters and there was even integrated billing with Stripe! Amicus also extensively used Wasp’s [Async Jobs](https://wasp-lang.dev/blog/2022/06/15/jobs-feature-announcement) feature to regularly update invoices, send reminder emails and clear out old data from the database.
|
||||
|
||||
After a few iterations with the legal team who were Amicus' test user (e.g. adding support for different types of users via roles), they were ready to get onboarded and become paying customers! More than 20 people from a single company are using Amicus daily for their work, making it an amazing source of continuous feedback for further development.
|
||||
|
||||
Erlis enjoyed the most how fast he could progress and ship features with Wasp on a weekly basis. Having both front-end, back-end, and database set and fully configured to work together from the beginning, he could focus on developing features rather than spend time figuring out the intricacies of the specific stack.
|
||||
|
||||
> If it weren't for Wasp, Amicus would probably have never been finished. I estimate it saved me 100+ hours from the start and I'm still amazed that I did all this work as a team-of-one. Being able to quickly change existing features and add the new ones is the biggest advantage of Wasp for me.
|
||||
>
|
||||
> — Erlis Kllogjri - Amicus
|
||||
|
||||
## Beyond MVP with Wasp
|
||||
|
||||
Although Erlis already has a product running in production, with first paying customers, he wants to see how far he can take it and has a lot of ideas (also requests) for the next features. *(Actually, Erlis had a big kanban board with post-its on a wall behind him as we were chatting, dedicated just to Amicus - that was impressive to see!)*.
|
||||
|
||||
Some of the most imminent ones are:
|
||||
|
||||
- uploading and sharing files between lawyers and clients
|
||||
- usage logging and analytics
|
||||
- transactional emails for notifications
|
||||
|
||||
Since under the hood Wasp is generating code in today's mainstream, production-tested technologies such as React, Node.js and PostgreSQL (through Prisma), there aren't any technical limitations to scaling Amicus as it grows and attracts more users.
|
||||
|
||||
Also, given that the `wasp build` CLI command generates a ready Docker image for the back-end (and static files for the front-end), deployment options are unlimited. Since Heroku is shutting down its free plan, we added guides on how to deploy your project for free on [Fly.io](http://Fly.io) and Railway (freemium).
|
||||
|
||||
> I was using Wasp while still in Alpha and was impressed how well everything worked, especially given how much stuff I get. I had just a few minor issues and the team responded super quickly on Discord and helped me resolve it.
|
||||
>
|
||||
> — Erlis Kllogjri - Amicus
|
70
web/blog/2022-11-26-michael-curry-usecase.md
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
title: 'How Michael Curry chose Wasp to build Grabbit: an internal tool for managing dev resources at StudentBeans'
|
||||
authors: [matijasos]
|
||||
image: /img/michael-curry-usecase/grabbit-hero-shot.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
|
||||
![grabbit hero shot](../static/img/michael-curry-usecase/grabbit-hero-shot.png)
|
||||
|
||||
[Michael Curry](https://github.com/cursorial) is a senior front-end engineer at [Improbable](https://www.improbable.io/), a metaverse and simulation company based in London. In his free time he enjoys learning about compilers.
|
||||
|
||||
In his previous position at StudentBeans, he experienced the problem of multiple engineering teams competing for the same dev environment (e.g. testing, staging, …). Then he discovered Wasp and decided to do something about it!
|
||||
|
||||
Read on to learn why Michael chose Wasp to build and deploy an internal tool for managing development environments at StudentBeans.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## The problem: the battle for the dev environment
|
||||
|
||||
StudentBeans has a microservices-based architecture with multiple environments - test, staging, production, …. The team practices CI/CD and deploys multiple times a day. With such a rapid development speed, it would relatively often happen that multiple engineering teams attempt to claim the same dev environment at the same time.
|
||||
|
||||
There wasn't an easy way for teams to synchronize on who is using which environment and it would eventually lead to unexpected changes, confusion, and prolonged development times.
|
||||
|
||||
## The solution: Grabbit - claim and release dev environments as-you-go
|
||||
|
||||
After the incident described above repeated for the n-th time, the team got together for a postmortem. They decided their new development process should look like this:
|
||||
|
||||
- merge your changes
|
||||
- claim the environment you want to deploy to (e.g. testing, staging, …)
|
||||
- deploy your changes
|
||||
- test your changes
|
||||
- release the environment once you are done with it so others are able to claim it
|
||||
|
||||
The other requirements were to build the solution in-house to save money and also not to spend more than a few hours on it as they still needed to deliver some important features for the ongoing sprint.
|
||||
|
||||
## The power of rapid prototyping with Wasp
|
||||
|
||||
Michael learned about Wasp during its [first HackerNews launch](https://news.ycombinator.com/item?id=26091956) and it immediately caught his eye. Being a programming language enthusiast himself, he immediately understood the value of a DSL approach and how it could drastically simplify the development process, while at the same time not preventing him from using his preferred tech stack (React, Node.js) when needed.
|
||||
|
||||
Also, although Michael had full-stack experience, his primary strength at the time was on the front-end side. Wasp looked like a great way of not having to deal with the tedious back-end setup and wiring (setting up the database, figuring out API, …) and being able to focus on the UX.
|
||||
|
||||
> When I first learned about Wasp on HN I was really excited about its DSL approach. It was amazing how fast I could get things running with Wasp - I had the first version within an hour! The language is also fairly simple and straightforward and plays well with React & Node.js + it removes a ton of boilerplate.
|
||||
>
|
||||
> — Michael Curry - Grabbit
|
||||
|
||||
## Out-of-the-box deployment
|
||||
|
||||
Once Michael was satisfied with the first version of Grabbit, and confirmed with the team it fits their desired process, the only thing left to do was to deploy it! It is well known this step can get really complicated, especially if you're not yet well-versed in the sea of config options that usually come with it.
|
||||
|
||||
Wasp CLI comes with a `wasp build` command that does all the heavy lifting for you - it creates a directory with static front-end files that you can easily deploy to e.g. Netlify, and on the other hand, a Docker image for the back-end. Since Heroku is ending its free plan, our recommendation is to deploy to Fly.io, for which the detailed guide is provided. You can find the [detailed deployment instructions here](https://wasp-lang.dev/docs/deploying).
|
||||
|
||||
In Michael's case, he deployed Grabbit behind the VPN since it was an internal tool, and this process was made easy by having a ready-to-go Dockerfile.
|
||||
|
||||
## From MVP to a full-fledged SaaS without a rewrite
|
||||
|
||||
The presented functionality of Grabbit above is quite simple (create a resource → claim it → release it), and it could have easily been implemented in some no-code tool or, if we really wanted to go simple, with a Trello board. So why use Wasp at all?
|
||||
|
||||
One reason is that developers know and prefer their tools and trust code over the no-code solutions, especially when requirements are still evolving and it is not evident they won't get "stuck" in some closed system. Michael had similar thinking - as he identified this problem at his own company, he realized others must be facing the same issue as well. That is why his plan was to keep improving Grabbit and eventually offer it as a standalone SaaS.
|
||||
|
||||
This is where Wasp comes in - he could develop and deploy an initial version of Grabbit in a matter of hours, but still end up with a platform that he can extend indefinitely through the power of code with his stack of choice, React & Node.js, while also using the npm packages he is using everyday at work.
|
||||
|
||||
Once he starts adding more advanced features, such as multi-user support with authentication, email notifications, and integration with CI/CD, no-code tools won't cut it any more. This way he saved himself and the company from throwing an MVP away and starting everything from scratch (having to learn the new technology and figure out how to set it all up) as the product evolves.
|
118
web/blog/2022-11-26-wasp-beta-launch-week.md
Normal file
@ -0,0 +1,118 @@
|
||||
---
|
||||
title: 'Wasp Beta Launch Week announcement'
|
||||
authors: [matijasos]
|
||||
image: /img/beta-ann/beta-banner.png
|
||||
tags: [webdev, wasp, startups, github]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
It’s almost here! After almost two years since our Alpha release, countless apps developed, React and Node versions upgraded, and PRs merged **we’re only a day away from Beta!**
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Beta is coming"
|
||||
source="img/beta-ann/beta-banner.png"
|
||||
/>
|
||||
|
||||
We’re going to follow a launch week format, **which means our Beta launch will last for the whole week**! Starting with the Product Hunt launch this Sunday (we’ll let you know once we’re live, so sharpen your upvoting fingers!) **we’ll highlight a new feature every day**.
|
||||
|
||||
I’ll try not to spoil too much in advance but we’re really excited about this - here follows a quick overview of what it’s gonna look like:
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Sunday, Nov 27 - Product Hunt launch event 🚀 + let’s get this party started: **Auth** 🎉
|
||||
|
||||
Besides defending our Product Hunt title (we won [#1 Product of the Day](https://www.producthunt.com/products/wasp-lang-alpha#wasp-lang-alpha) last time), this time we’ll also have an online party for all of us to celebrate together!
|
||||
|
||||
It will be held **on our Discord at 9:00 am EST / 15:00 CET** - [sign up here](https://discord.gg/4kUcXChX?event=1042717917097246720) and make sure to mark yourself as “Interested”!
|
||||
|
||||
Join us to meet the team, attend a relaxed AMA session to learn everything about Wasp, from how it started to development challenges (having fun with Haskell, web dev and compilers) and ideas and plans for the future.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Beta launch party instructions"
|
||||
source="img/beta-ann/launch-party.png"
|
||||
/>
|
||||
|
||||
The first feature to announce will be authentication in Wasp! It’s easier and cooler than ever, supports 3rd party providers (hint: starts with “G”), and works smoother than a jar of peanut butter (not the crunchy one of course)!
|
||||
|
||||
## Monday, Nov 28 - TypeScript support!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="TypeScript is here!"
|
||||
source="img/beta-ann/thank-you-god.gif"
|
||||
/>
|
||||
|
||||
When we asked you what was missing in Wasp during our [Alpha Testing Program](/blog/2022/11/16/alpha-testing-program-post-mortem), you were pretty clear:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="TypeScript is wanted!"
|
||||
source="img/beta-ann/ts-wanted.png"
|
||||
/>
|
||||
|
||||
We heard you (honestly we were missing it too) and now it’s here! You can write your code in TypeScript and enjoy all the goodies that types bring. Some things already work really well and there are a few for which we still have ideas on how to make them better, but more on that on Tuesday!
|
||||
|
||||
## Wednesday, Nov 29 - Tailwind support! 🐈 💨
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Tailwind Nic Cage"
|
||||
source="img/beta-ann/nic-cage-tailwind.gif"
|
||||
/>
|
||||
|
||||
It’s beautiful! Another highly anticipated featured that also comes with Beta - support for Tailwind CSS framework! Since it has an additional build step it didn’t work out-of-the-box with Alpha, but now it works like a breeze (see what I did here?)!
|
||||
|
||||
Honestly, having used it for designing our new Beta landing page I can really see why it gained so much popularity. So long, making up names for classes, “containers”, and “wrappers”!
|
||||
|
||||
## Thursday, Nov 30 - Optimistic updates!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Without optimistic updates"
|
||||
source="img/beta-ann/no-opt-updates.gif"
|
||||
caption="Stop glitching, dang it!"
|
||||
/>
|
||||
|
||||
You know that feeling when you move your Trello card “Try Wasp Beta” from “Todo” column to “Done” column and everything works super smoothly without any glitches? That’s because of optimistic updates! You may not need it often but if you needed and it wasn’t possible you’d feel really sad.
|
||||
|
||||
Well, that’s why Alpha is called Alpha and Beta is called Beta 😅. Long story short, now it’s possible to do it in Wasp and it’s also super easy and clean! We're actually very optimistic you’ll feel really good about implementing optimistic updates for your app in Wasp.
|
||||
|
||||
## Friday, Dec 1 - Improved IDE support, tooling and Wasp LSP!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="VS Code support for Wasp LSP"
|
||||
source="img/beta-ann/wasp-loves-vscode.png"
|
||||
/>
|
||||
|
||||
If you like types in TypeScript (and in general), then you will also enjoy Wasp! Our DSL is also a typed language which means it can report errors in compile time, e.g. in case you haven’t configured your route correctly. And now all that happens directly in your editor!
|
||||
|
||||
**Beta brings LSP, Language Server for Wasp that works with VS Code** (support for other editors coming soon! I’m VIM user myself so take a guess :D). That means improved syntax highlighting, code autocompletion and live error reporting - everything you’d expect from a language!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Wasp Language Server in action"
|
||||
source="img/beta-ann/wls-demo.gif"
|
||||
caption="Wasp LSP in action!"
|
||||
/>
|
||||
|
||||
## Saturday, Dec 2 - Grande Finale + #1 Wasp Hackathon!(Waspathon🐝 ?)
|
||||
|
||||
<ImgWithCaption
|
||||
alt="First Wasp hackathon"
|
||||
source="img/beta-ann/hackathon-banner.gif"
|
||||
/>
|
||||
|
||||
I don’t want to reveal too much in advance, but yep there will be a hackathon, yep there will be cool rewards (at least we think so) and yep it will be awesome! We’ll officially announce it as we end the launch week, and equipped with all the new features Beta brought we’ll switch into the hacking mode!
|
||||
|
||||
It’s our first hackathon and we can’t wait to tell you more about it (ok, I admit, we’re still working on it) and see what you beeld with Wasp!
|
||||
|
||||
## Recap
|
||||
|
||||
- **We are launching Beta this Sunday, Nov 27, on Product Hunt at 1am PST / 4am EST / 10am CET** - make sure to upvote and comment (anything counts, even “go guys!”) when you can
|
||||
- **Beta brings a ton of new exciting features** - we’ll highlight one each day of the following week
|
||||
- **On Saturday, Dec 2, we’ll announce a hackathon** - our first ever!
|
||||
|
||||
That’s it, Waspeteers - keep buzzing as always and see you soon on the other side! 🐝 🅱️
|
||||
|
||||
Matija, Martin & the Wasp team
|
11
web/blog/components/DiscordLink.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
const DiscordLink = (props) => {
|
||||
return (
|
||||
<span>
|
||||
<a href="https://discord.gg/rzdnErX"> Discord </a>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscordLink;
|
@ -4,9 +4,16 @@ sidebar_label: Contributing
|
||||
slug: /contributing
|
||||
---
|
||||
|
||||
Any way you want to contribute is a good way, and we are happy to meet you :)!
|
||||
import DiscordLink from '../blog/components/DiscordLink';
|
||||
|
||||
Some typical ways to contribute:
|
||||
1. Join us on discord [![Discord](https://img.shields.io/discord/686873244791210014?label=chat%20on%20discord)](https://discord.gg/rzdnErX) and let's talk! We can discuss language design, new/existing features, weather, or you can just tell us how you feel about Wasp :).
|
||||
2. If there is something in docs that you think could be improved or clarified, go to [docs Github repo](https://github.com/wasp-lang/wasp) and make an issue/PR! Or, you can do it directly from here by clicking on "edit this page" at the bottom of each page.
|
||||
3. Create an issue/PR on [core Wasp repo](https://github.com/wasp-lang/wasp) to contribute directly to the language and/or compiler! Check [wapsc README](https://github.com/wasp-lang/wasp/tree/main/waspc) for more details.
|
||||
Any way you want to contribute is a good way, and we'd be happy to meet you! A single entry point for all contributors is the [CONTRIBUTING.md](https://github.com/wasp-lang/wasp/blob/main/CONTRIBUTING.md) file in our Github repo. All the requirements and instructions are there, so please check [CONTRIBUTING.md](https://github.com/wasp-lang/wasp/blob/main/CONTRIBUTING.md) for more details.
|
||||
|
||||
Some side notes to make your journey easier:
|
||||
|
||||
1. Join us on <DiscordLink /> and let's talk! We can discuss language design, new/existing features, and weather, or you can tell us how you feel about Wasp :).
|
||||
|
||||
2. Wasp's compiler is built with Haskell. That means you'll need to be somewhat familiar with this language if you'd like to contribute to the compiler itself. But Haskell is just a part of Wasp, and you can contribute to lot of parts that require web dev skills, either by coding or by suggesting how to improve Wasp and its design as a web framework. If you don't have Haskell knowledge (or any dev experience at all) - no problem. There are a lot of JS-related tasks and documentation updates as well!
|
||||
|
||||
3. If there's something you'd like to bring to our attention, go to [docs GitHub repo](https://github.com/wasp-lang/wasp) and make an issue/PR!
|
||||
|
||||
Happy hacking!
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Deploying
|
||||
---
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
:::info
|
||||
Wasp is in beta, so keep in mind there might be some kinks / bugs, and possibly a bit bigger changes in the future.
|
||||
@ -11,77 +12,193 @@ Right now, deploying of Wasp project is done by generating the code and then dep
|
||||
|
||||
In the future, the plan is to have Wasp take care of it completely: you would declaratively define your deployment in .wasp and then just call `wasp deploy` ([github issue](https://github.com/wasp-lang/wasp/issues/169)).
|
||||
|
||||
If you want to deploy your App completely **free** of charge, continue reading below for guides on using Fly.io as your backend (server) provider and Netlify for your frontend (client).
|
||||
|
||||
If you prefer to host client and server on **one platform**, and don't mind paying a very small fee for extra features, we suggest following the guide on using [Railway as your provider](#deploying-to-railway-freemium-all-in-one-solution).
|
||||
|
||||
## Generating deployable code
|
||||
|
||||
```
|
||||
wasp build
|
||||
```
|
||||
|
||||
generates deployable code for the whole app in the `.wasp/build/` directory. Next, we will deploy this code.
|
||||
|
||||
NOTE: You will not be able to build the app if you are using SQLite as a database (which is a default database) -> you will have to [switch to PostgreSQL](/docs/language/features#migrating-from-sqlite-to-postgresql).
|
||||
|
||||
## Deploying API server (backend)
|
||||
|
||||
In `.wasp/build/`, there is a `Dockerfile` describing an image for building the server.
|
||||
|
||||
To run server in production, deploy this docker image to your favorite hosting provider, ensure that env vars are correctly set, and that is it.
|
||||
|
||||
Below we will explain the required env vars and also provide detailed instructions for deploying to Heroku.
|
||||
Below we will explain the required env vars and also provide detailed instructions for deploying to Fly.io or Heroku.
|
||||
|
||||
### Env vars
|
||||
|
||||
Server uses following environment variables, so you need to ensure they are set on your hosting provider:
|
||||
|
||||
- `PORT` -> The port number at which it will listen for requests (e.g. `3001`).
|
||||
- `DATABASE_URL` -> The URL of the Postgres database it should use (e.g. `postgresql://mydbuser:mypass@localhost:5432/nameofmydb`).
|
||||
- `WASP_WEB_CLIENT_URL` -> The URL of where the frontend app is running (e.g. `https://<app-name>.netlify.app`), which is necessary for CORS.
|
||||
- `JWT_SECRET` -> You need this if you are using Wasp's `auth` feature. Set it to a random string (password), at least 32 characters long.
|
||||
|
||||
### Deploying to Heroku
|
||||
### Deploying to Fly.io (free, recommended)
|
||||
|
||||
Fly.io offers a variety of free services that are perfect for deploying your first Wasp app! You will need a Fly.io account and the [`flyctl` CLI](https://fly.io/docs/hands-on/install-flyctl/).
|
||||
|
||||
:::note
|
||||
Fly.io offers support for both locally built Docker containers and remotely built ones. However, for simplicity and reproducability, we will force the use of a remote Fly.io builder.
|
||||
|
||||
Additionally, `fly` is a symlink for `flyctl` on most systems and they can be used interchangeably.
|
||||
:::
|
||||
|
||||
Make sure you are logged in with `flyctl` CLI. You can check if you are logged in with `flyctl auth whoami`, and if you are not, you can log in with `flyctl auth login`.
|
||||
|
||||
#### Set up a Fly.io app (only once per Wasp app)
|
||||
|
||||
Unless you already have a Fly.io app that you want to deploy to, let's create a new Fly.io app. Position yourself in .wasp/build/ directory (reminder: which you created by running `wasp build` previously):
|
||||
|
||||
```bash
|
||||
cd .wasp/build
|
||||
```
|
||||
|
||||
Now from within the `build` directory, run the launch command to set up a new app and create a `fly.toml` file:
|
||||
|
||||
```bash
|
||||
flyctl launch --remote-only
|
||||
```
|
||||
|
||||
This will ask a series of questions, including what region to deploy in and if you would like a database.
|
||||
|
||||
- Say **yes to "Would you like to set up a Postgresql database now?", and select Development**, and Fly.io will set a `DATABASE_URL` for you.
|
||||
- Say **no to "Would you like to deploy now?"**, as well as any additional questions. We still need to set a few environment variables.
|
||||
|
||||
:::note
|
||||
If your attempts to initiate a new app fail for whatever reason, then you can run `flyctl apps destroy <app-name>` before trying again.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
What does it look like when your DB is deployed correctly?
|
||||
</summary>
|
||||
<div>
|
||||
<p>When your DB is deployed correctly, you will be able to view it in the <a href="https://fly.io/dashboard">Fly.io dashboard</a>:</p>
|
||||
<img width="662" alt="image" src="https://user-images.githubusercontent.com/70215737/201068630-d100db2c-ade5-4874-a29f-6e1890dba2fc.png" />
|
||||
</div>
|
||||
</details>
|
||||
:::
|
||||
|
||||
Next, let's copy the `fly.toml` file up to our Wasp project dir for safekeeping.
|
||||
```bash
|
||||
cp fly.toml ../../
|
||||
```
|
||||
|
||||
Next, let's add a few more environment variables:
|
||||
|
||||
```bash
|
||||
flyctl secrets set PORT=8080
|
||||
flyctl secrets set JWT_SECRET=<random_string_at_least_32_characters_long>
|
||||
flyctl secrets set WASP_WEB_CLIENT_URL=<url_of_where_frontend_will_be_deployed>
|
||||
```
|
||||
|
||||
NOTE: If you do not know what your frontend URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your frontend.
|
||||
|
||||
If you want to make sure you've added your secrets correctly, run `flyctl secrets list` in the terminal. Note that you will see hashed versions of your secrets to protect your sensitive data.
|
||||
|
||||
#### Deploy to a Fly.io app
|
||||
|
||||
While still in the .wasp/build/ directory, run:
|
||||
|
||||
```bash
|
||||
flyctl deploy --remote-only --config ../../fly.toml
|
||||
```
|
||||
|
||||
This will build and deploy the backend of your Wasp app on Fly.io to `https://<app-name>.fly.dev`! 🤘🎸
|
||||
|
||||
Now, if you haven't, you can deploy your frontend -- [we suggest using Netlify](#deploying-web-client-frontend) for this -- and add the client url by running `flyctl secrets set WASP_WEB_CLIENT_URL=<url_of_deployed_frontend>`
|
||||
|
||||
Additionally, some useful commands include:
|
||||
|
||||
```bash
|
||||
flyctl logs
|
||||
flyctl secrets list
|
||||
flyctl ssh console
|
||||
```
|
||||
|
||||
#### Redeploying after Wasp builds
|
||||
|
||||
When you rebuild your Wasp app (with `wasp build`), it will remove your .wasp/build/ directory. In there, you may have a `fly.toml` from any prior Fly.io deployments. While we will improve this process in the future, in the meantime, you have a few options:
|
||||
|
||||
1. Copy the `fly.toml` file to a versioned directory, like your Wasp project dir. From there, you can reference it in `flyctl deploy --config <path>` commands, like above.
|
||||
1. Backup the `fly.toml` file somewhere before running `wasp build`, and copy it into .wasp/build/ after. When the `fly.toml` file exists in .wasp/build/ dir, you do not need to specify the `--config <path>`.
|
||||
1. Run `flyctl config save -a <app-name>` to regenerate the `fly.toml` file from the remote state stored in Fly.io.
|
||||
|
||||
### Deploying to Heroku (non-free)
|
||||
|
||||
:::note
|
||||
Heroku used to offer free apps under certain limits. However, as of November 28, 2022, they ended support for their free tier. https://blog.heroku.com/next-chapter
|
||||
|
||||
As such, we recommend using an alternative provider like [Fly.io](#deploying-to-flyio-free-recommended) for your first apps.
|
||||
:::
|
||||
|
||||
Heroku is completely free under certain limits, so it is ideal for getting started with deploying a Wasp app.
|
||||
You will need Heroku account, `heroku` CLI and `docker` CLI installed to follow these instructions.
|
||||
|
||||
Make sure you are logged in with `heroku` CLI. You can check if you are logged in with `heroku whoami`, and if you are not, you can log in with `heroku login`.
|
||||
|
||||
#### Set up a Heroku app (only once per Wasp app)
|
||||
|
||||
Unless you already have a heroku app that you want to deploy to, let's create a new Heroku app:
|
||||
|
||||
```
|
||||
heroku create <app-name>
|
||||
```
|
||||
|
||||
Unless you have external Postgres database that you want to use, let's create new database on Heroku and attach it to our app:
|
||||
|
||||
```
|
||||
heroku addons:create --app <app-name> heroku-postgresql:hobby-dev
|
||||
```
|
||||
|
||||
Heroku will also set `DATABASE_URL` env var for us at this point. If you are using external database, you will have to set it yourself.
|
||||
|
||||
The `PORT` env var will also be provided by Heroku, so the only two left to set are the `JWT_SECRET` and `WASP_WEB_CLIENT_URL` env vars:
|
||||
|
||||
```
|
||||
heroku config:set --app <app-name> JWT_SECRET=<random_string_at_least_32_characters_long>
|
||||
heroku config:set --app <app-name> WASP_WEB_CLIENT_URL=<url_of_where_frontend_will_be_deployed>
|
||||
```
|
||||
|
||||
NOTE: If you do not know what your frontend URL is yet, don't worry. You can set WASP_WEB_CLIENT_URL after you deploy your frontend.
|
||||
NOTE: If you do not know what your frontend URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your frontend.
|
||||
|
||||
#### Deploy to a Heroku app
|
||||
|
||||
Position yourself in `.wasp/build/` directory (reminder: which you created by running `wasp build` previously):
|
||||
|
||||
```
|
||||
cd .wasp/build
|
||||
```
|
||||
|
||||
assuming you were at the root of your Wasp project at that moment.
|
||||
|
||||
Log in to Heroku Container Registry:
|
||||
|
||||
```
|
||||
heroku container:login
|
||||
```
|
||||
|
||||
Build the docker image and push it to Heroku:
|
||||
|
||||
```
|
||||
heroku container:push --app <app-name> web
|
||||
```
|
||||
|
||||
App is still not deployed at this point.
|
||||
This step might take some time, especially the very first time, since there are no cached docker layers.
|
||||
|
||||
:::note
|
||||
|
||||
#### Note for Apple M1 users
|
||||
|
||||
Apple M1 users need to build a non-Arm image, so the above step will not work at this time. Instead of `heroku container:push`, users instead should:
|
||||
|
||||
```bash
|
||||
@ -94,34 +211,49 @@ You are now ready to proceed to the next step.
|
||||
:::
|
||||
|
||||
Deploy the pushed image and restart the app:
|
||||
|
||||
```
|
||||
heroku container:release --app <app-name> web
|
||||
```
|
||||
|
||||
This is it, backend is deployed at `https://<app-name>.herokuapp.com`!
|
||||
|
||||
Additionally, you can check out the logs with:
|
||||
|
||||
```
|
||||
heroku logs --tail --app <app-name>
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
#### Note on using pg-boss with Heroku
|
||||
|
||||
If you wish to deploy an app leveraging Jobs that use pg-boss as the executor to Heroku, you need to set an additional environment variable called `PG_BOSS_NEW_OPTIONS` to `{"connectionString":"<REGULAR_HEROKU_DATABASE_URL>","ssl":{"rejectUnauthorized":false}}`. This is because pg-boss uses the `pg` extension, which does not seem to connect to Heroku over SSL by default, which Heroku requires. Additionally, Heroku uses a self-signed cert, so we must handle that as well.
|
||||
|
||||
- https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
|
||||
:::
|
||||
|
||||
## Deploying web client (frontend)
|
||||
|
||||
Position yourself in `.wasp/build/web-app` directory (reminder: which you created by running `wasp build` previously):
|
||||
|
||||
```
|
||||
cd .wasp/build/web-app
|
||||
```
|
||||
|
||||
assuming you were at the root of your Wasp project at that moment.
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
npm install && REACT_APP_API_URL=<url_to_wasp_backend> npm run build
|
||||
```
|
||||
where <url_to_wasp_backend> is url of the wasp backend that you previously deployed, e.g. `https://wasp-test.herokuapp.com`.
|
||||
:::info NO SLASH
|
||||
Make sure your API URL does <strong>not</strong> have a trailing "/" on the end of it:<br/>
|
||||
✅ https://backend.example.com <br/>❌ https://backend.example.com/
|
||||
:::
|
||||
|
||||
where <url_to_wasp_backend> is url of the wasp backend that you previously deployed, e.g. `https://wasp-test.fly.dev`.
|
||||
|
||||
This will create `build/` directory, which you can deploy to any static hosting provider.
|
||||
Check instructions below for deploying to Netlify.
|
||||
@ -134,14 +266,131 @@ You will need Netlify account and `netlify` CLI installed to follow these instru
|
||||
Make sure you are logged in with `netlify` CLI. You can check if you are logged in with `netlify status`, and if you are not, you can log in with `netlify login`.
|
||||
|
||||
While positioned in `.wasp/build/web-app/` directory, and after you have created `.wasp/build/web-app/build/` directory as per instructions above, run
|
||||
|
||||
```
|
||||
netlify deploy
|
||||
```
|
||||
|
||||
and carefully follow their instructions (i.e. do you want to create a new app or use existing one, team under which your app will reside, ..., final step to run `netlify deploy --prod`).
|
||||
|
||||
That is it!
|
||||
|
||||
NOTE: Make sure you set this URL as the `WASP_WEB_CLIENT_URL` environment variable in Heroku.
|
||||
NOTE: Make sure you set this URL as the `WASP_WEB_CLIENT_URL` environment variable in your server hosting environment (e.g., Fly.io or Heroku).
|
||||
|
||||
## Deploying to Railway ("freemium", all-in-one solution)
|
||||
|
||||
Railway makes it easy to deploy your entire app -- database, server, and client -- on one platform. You can use the platform for free for a limited time (~21 days) per month. Upgrading to the `Developer` plan will only cost you a few dollays per month per service.
|
||||
|
||||
:::danger 🛑
|
||||
Due to Railway's current proxy configuration, Google Auth will not currently work. If you're using Google Auth in your Wasp App, you can still deploy your back-end to Railway, but we suggest you [deploy your front-end to Netlify](#deploying-to-netlify)
|
||||
:::
|
||||
|
||||
To get started, follow these steps:
|
||||
|
||||
1. [Generate deployable code](#generating-deployable-code) (`wasp build`)
|
||||
2. Sign up at [Railway.app](https://railway.app) (Tip! Sign up with your GitHub account for $5/month of usage free)
|
||||
3. Before creating a new project, install the [Railway CLI](#https://docs.railway.app/develop/cli#install) by running the following command in your terminal:
|
||||
```shell
|
||||
curl -fsSL https://railway.app/install.sh | sh
|
||||
```
|
||||
4. While still in the terminal, run `railway login` and a browser tab will open to authenticate you.
|
||||
|
||||
#### Create New Project
|
||||
|
||||
Go back to your [Railway dashboard](https://railway.app/dashboard), click on **+ New Project**, and select `Provision PostgreSQL` from the dropdown menu.
|
||||
|
||||
Once it initializes, right click on the `+ New` button in the top right corner and select `>_ Empty Service`. Once it initializes, click on it, go to `Settings > General` and change the name (e.g. `server`).
|
||||
|
||||
Go ahead and create another empty service and name it (e.g. `client`).
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<em>Just in case, here is a helpful screenshot ;)</em>
|
||||
</summary>
|
||||
<div>
|
||||
<img alt="Create an Empty Service"
|
||||
src={useBaseUrl('img/deploying/railway-rename.png')} />
|
||||
</div>
|
||||
</details>
|
||||
|
||||
#### Deploy to services
|
||||
|
||||
Now go back to your terminal and execute the following commands:
|
||||
|
||||
1. Move into your app's `.wasp/build/` directory, which was created when you ran `wasp build` previously:
|
||||
```shell
|
||||
cd .wasp/build
|
||||
```
|
||||
2. "Link" your app build to your newly created Railway project:
|
||||
```shell
|
||||
railway link
|
||||
```
|
||||
3. Push and deploy the project to railway (make sure you're in `.wasp/build`):
|
||||
```shell
|
||||
railway up
|
||||
```
|
||||
Select `server` when prompted with `Select Service`. Press enter.
|
||||
Railway will now locate the Dockerfile and deploy your server 👍
|
||||
|
||||
When deployment is finished, you will see: `Deployment live at <url_to_wasp_backend>`
|
||||
Copy this URL 📜. We need it for step 5!
|
||||
|
||||
4. Next, change into your app's frontend build directory `.wasp/build/web-app`:
|
||||
```shell
|
||||
cd web-app
|
||||
```
|
||||
5. Create the production build, adding the URL from step 3:
|
||||
```shell
|
||||
npm install && REACT_APP_API_URL=<url_to_wasp_backend> npm run build
|
||||
```
|
||||
:::info NO SLASH
|
||||
Make sure your API URL does <strong>not</strong> have a trailing "/" on the end of it:<br/>
|
||||
✅ https://backend.example.com <br/>❌ https://backend.example.com/
|
||||
:::
|
||||
|
||||
6. Change into the `.wasp/build/web-app/build` directory and deploy:
|
||||
```shell
|
||||
cd build && railway up
|
||||
```
|
||||
This time select `client` when prompted with `Select Service`.
|
||||
|
||||
7. Your apps are deployed 🧙♂️. Now it's time to add environment variables, so open the project in the browser
|
||||
```shell
|
||||
railway open
|
||||
```
|
||||
|
||||
#### Add Environment Variables
|
||||
|
||||
Back in your [Railway dashboard](https://railway.app/dashboard), click on your project and you should see your newly deployed services: Postgres, Server, and Client.
|
||||
|
||||
Now you're going to pass each service the correct [environment variables](#env-vars). To do this, you first need to tell Railway to generate public domains for client and server.
|
||||
|
||||
Go to the server instance's `Settings` tab, and click `Generate Domain`. Do the same under the client's `Settings`.
|
||||
|
||||
The Postgres database is already initialized with a domain, so click on the Postgres instance, go to the **Connect** tab and copy the `Postgres Connection URL`.
|
||||
|
||||
Go back to your `server` instance and navigate to its `Variables` tab. Now add the copied Postgres URL as `DATABASE_URL`, as well as the client's domain as `WASP_WEB_CLIENT_URL`.
|
||||
|
||||
Next, copy the server's domain, move over to the client's `Variables` tab and add the generated server domain as a new variable called `REACT_APP_API_URL`.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<em>Having trouble finding these settings?</em>
|
||||
</summary>
|
||||
|
||||
<div>
|
||||
<figure>
|
||||
<img src={useBaseUrl('img/deploying/railway-postgres-url.png')}/>
|
||||
<figcaption class="image-caption">Postgres Connection URL</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src={useBaseUrl('img/deploying/railway-server-var.png')}/>
|
||||
<figcaption class="image-caption">Env Variables</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
And now you should be deployed! 🐝 🚂 🚀
|
||||
|
||||
## Customizing the Dockerfile
|
||||
By default, Wasp will generate a multi-stage Dockerfile that is capable of building an image with your Wasp-generated server code and running it, along with any pending migrations, as in the deployment scenario above. If you need to customize this Dockerfile, you may do so by adding a Dockerfile to your project root directory. If present, Wasp will append the contents of this file to the _bottom_ of our default Dockerfile.
|
||||
@ -154,3 +403,6 @@ Since the last definition in a Dockerfile wins, you can override or continue fro
|
||||
To see what your project's (potentially combined) Dockerfile will look like, run: `wasp dockerfile`
|
||||
|
||||
Here are the official docker docs on [multi-stage builds](https://docs.docker.com/build/building/multi-stage/). Please join our Discord if you have any questions, or if the customization hook provided here is not sufficient for your needs!
|
||||
|
||||
#### Updates & Redeploying
|
||||
When you make updates and need to redeploy, just follow [steps 3-7](#deploy-to-services) above. Remember, you can connect or disconnect your app to any project in your Railway account by using `railway link` or `railway unlink` from within the app's directory.
|
@ -53,17 +53,17 @@ We recommend using [nvm](https://github.com/nvm-sh/nvm) for managing your Node.j
|
||||
</div>
|
||||
</details>
|
||||
|
||||
:::info
|
||||
<details>
|
||||
<summary style={{cursor: 'pointer', 'textDecoration': 'underline'}}>
|
||||
Why this version of node?
|
||||
</summary>
|
||||
<div>
|
||||
At Wasp, we focus on supporting the latest LTS ("long-term-support") Node.js version, since it guarantees stability and active maintainance, which is why the official Node.js team recommends it for usage in production.
|
||||
Therefore, a specific Wasp release will usually require the version of Node.js that was LTS at that point of time.
|
||||
Check out https://nodejs.org/en/about/releases/ for more details about Node.js releases.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
Why does Wasp require this specific `node` range and doesn't support a newer version x.y.z?
|
||||
|
||||
At Wasp, we focus on supporting the latest LTS ("long-term-support") Node.js version, since it guarantees stability and active maintainance, which is why the official Node.js team recommends it for usage in production.
|
||||
Therefore, a specific Wasp release will usually require the version of Node.js that was LTS at that point of time.
|
||||
Check out https://nodejs.org/en/about/releases/ for more details about Node.js releases.
|
||||
|
||||
Sometimes we will make an exception to that and additionally limit the Node.js version or postpone switching to the latest LTS if there are certain issues with new Node.js version, in which case we will catch up once those are resolved on Node.js side or we find a workaround on Wasp side.
|
||||
|
||||
:::
|
||||
|
||||
## 2. Installation
|
||||
|
||||
@ -140,7 +140,7 @@ The extension brings the following functionality:
|
||||
|
||||
## 4. What next?
|
||||
|
||||
**Check out the 🤓 [Todo App tutorial](tutorials/todo-app.md) 🤓 , which will take you through all the core features of Wasp!**
|
||||
**Check out the 🤓 [Pick a Tutorial page](pick-a-tutorial.md) 🤓. Choose an app that you'd like to build and it will take you through all the core features of Wasp!**
|
||||
|
||||
Also, we would be excited to have you **join our community on [Discord](https://discord.gg/rzdnErX)!** Any feedback or questions you have, we are there for you.
|
||||
|
||||
|
@ -7,7 +7,7 @@ title: Features
|
||||
There can be only one declaration of `app` type per Wasp project.
|
||||
It serves as a starting point and defines global properties of your app.
|
||||
|
||||
```css
|
||||
```c
|
||||
app todoApp {
|
||||
wasp: {
|
||||
version: "^0.6.0"
|
||||
@ -59,7 +59,7 @@ Check [`app.dependencies`](/docs/language/features#dependencies) for more detail
|
||||
|
||||
`page` declaration is the top-level layout abstraction. Your app can have multiple pages.
|
||||
|
||||
```css
|
||||
```c
|
||||
page MainPage {
|
||||
component: import Main from "@client/pages/Main",
|
||||
authRequired: false // optional
|
||||
@ -80,7 +80,7 @@ If set to `true`, only authenticated users will be able to access this page. Una
|
||||
|
||||
If `authRequired` is set to `true`, the React component of a page (specified by `component` property) will be provided `user` object as a prop.
|
||||
|
||||
Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/auth#updating-main-page-to-check-if-user-is-authenticated) for an example of usage.
|
||||
Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/06-auth#updating-main-page-to-check-if-user-is-authenticated) for an example of usage.
|
||||
|
||||
## Route
|
||||
|
||||
@ -622,7 +622,7 @@ Keep in mind that pg-boss jobs run alongside your other server-side code, so the
|
||||
|
||||
To declare a `job` in Wasp, simply add a declaration with a reference to an `async` function, like the following:
|
||||
|
||||
```css title="main.wasp"
|
||||
```c title="main.wasp"
|
||||
job mySpecialJob {
|
||||
executor: PgBoss,
|
||||
perform: {
|
||||
@ -649,7 +649,7 @@ And that is it! Your job will be executed by the job executor (pg-boss, in this
|
||||
|
||||
If you have work that needs to be done on some recurring basis, you can add a `schedule` to your job declaration:
|
||||
|
||||
```css {6-9} title="main.wasp"
|
||||
```c {6-9} title="main.wasp"
|
||||
job mySpecialJob {
|
||||
executor: PgBoss,
|
||||
perform: {
|
||||
@ -667,7 +667,7 @@ In this example, you do _not_ need to invoke anything in JavaScript. You can ima
|
||||
### Fully specified example
|
||||
Both `perform` and `schedule` accept `executorOptions`, which we pass directly to the named job executor when you submit jobs. In this example, the scheduled job will have a `retryLimit` set to 0, as `schedule` overrides any similar property from `perform`. Lastly, we add an entity to pass in via the context argument to `perform.fn`.
|
||||
|
||||
```css
|
||||
```c
|
||||
job mySpecialJob {
|
||||
executor: PgBoss,
|
||||
entities: [Task],
|
||||
@ -799,10 +799,10 @@ In the future, we will add support for picking any version you like, but we have
|
||||
|
||||
Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by configuring `auth` field of the `app` declaration:
|
||||
|
||||
```css
|
||||
```c
|
||||
app MyApp {
|
||||
title: "My app",
|
||||
// ...
|
||||
//...
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
@ -828,7 +828,7 @@ List of authentication methods that Wasp app supports. Currently supported metho
|
||||
|
||||
#### `onAuthFailedRedirectTo: String` (required)
|
||||
Path where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting `authRequired: true` for a specific page).
|
||||
Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/auth#updating-main-page-to-check-if-user-is-authenticated) to see an example of usage.
|
||||
Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/06-auth#updating-main-page-to-check-if-user-is-authenticated) to see an example of usage.
|
||||
|
||||
#### `onAuthSucceededRedirectTo: String` (optional)
|
||||
Path where a successfully authenticated user will be sent upon successful login/signup.
|
||||
@ -856,7 +856,7 @@ The quickest way to get started is by using the following API generated by Wasp:
|
||||
- `useAuth()` React hook
|
||||
**NOTE:** If the signup is successful, the Signup form will automatically log in the user.
|
||||
|
||||
Check our [Todo app tutorial](/docs/tutorials/todo-app/auth) to see how it works. See below for detailed specification of each of these methods.
|
||||
Check our [Todo app tutorial](/docs/tutorials/todo-app/06-auth) to see how it works. See below for detailed specification of each of these methods.
|
||||
|
||||
#### Lower-level API
|
||||
|
||||
@ -867,28 +867,35 @@ If you require more control in your authentication flow, you can achieve that in
|
||||
The code of your custom sign-up action would look like this (your user entity being `User` in this instance):
|
||||
```js
|
||||
export const signUp = async (args, context) => {
|
||||
// Your custom code before sign-up.
|
||||
// ...
|
||||
const newUser = context.entities.User.create({
|
||||
data: { username: 'waspeteer', password: 'this will be hashed!' }
|
||||
})
|
||||
// Your custom code before sign-up.
|
||||
// ...
|
||||
|
||||
// Your custom code after sign-up.
|
||||
// ...
|
||||
return newUser
|
||||
const newUser = context.entities.User.create({
|
||||
data: {
|
||||
username: args.username,
|
||||
password: args.password // password hashed automatically by Wasp! 🐝
|
||||
}
|
||||
})
|
||||
|
||||
// Your custom code after sign-up.
|
||||
// ...
|
||||
return newUser
|
||||
}
|
||||
```
|
||||
|
||||
:::info
|
||||
You don't need to worry about hashing the password yourself! Even when you are using Prisma's client directly and calling `create()` with a plain-text password, Wasp put middleware in place that takes care of hashing it before storing it to the database. An additional middleware also performs field validation.
|
||||
You don't need to worry about hashing the password yourself! Even when you are using Prisma's client directly and calling `create()` with a plain-text password, Wasp's middleware takes care of hashing it before storing it in the database. An additional middleware also performs field validation.
|
||||
:::
|
||||
|
||||
##### Customizing user entity validations
|
||||
|
||||
To disable/enable default validations, or add your own, you can do:
|
||||
To disable/enable default validations, or add your own, you can modify your custom signUp function like so:
|
||||
```js
|
||||
const newUser = context.entities.User.create({
|
||||
data: { username: 'waspeteer', password: 'this will be hashed!' },
|
||||
data: {
|
||||
username: args.username,
|
||||
password: args.password // password hashed automatically by Wasp! 🐝
|
||||
},
|
||||
_waspSkipDefaultValidations: false, // can be omitted if false (default), or explicitly set to true
|
||||
_waspCustomValidations: [
|
||||
{
|
||||
@ -1082,9 +1089,24 @@ import AuthError from '@wasp/core/AuthError.js'
|
||||
|
||||
### Google
|
||||
|
||||
`google` authentication makes it possible to use Google's OAuth 2.0 service to sign Google users into your app. To enable it, add `google: {}` to your `auth.methods` dictionary to use it with default settings. If you require custom configuration setup or user entity field assignment, you can override the defaults.
|
||||
`google` authentication makes it possible to use Google's OAuth 2.0 service to sign Google users into your app. To enable it, add `google: {}` to your `auth.methods` dictionary to use it with default settings:
|
||||
|
||||
```js
|
||||
//...
|
||||
|
||||
auth: {
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
google: {}
|
||||
},
|
||||
//...
|
||||
}
|
||||
```
|
||||
This method requires also requires that `externalAuthEntity` be specified in `auth` as [described here](features#externalauthentity).
|
||||
|
||||
If you require custom configuration setup or user entity field assignment, you can [override the defaults](#overrides).
|
||||
|
||||
This method requires that `externalAuthEntity` specified in `auth` [described here](features#externalauthentity).
|
||||
#### Default settings
|
||||
- Configuration:
|
||||
- By default, Wasp expects you to set two environment variables in order to use Google authentication:
|
||||
@ -1093,8 +1115,12 @@ This method requires that `externalAuthEntity` specified in `auth` [described he
|
||||
- These can be obtained in your Google Cloud Console project dashboard. See [here](/docs/integrations/google#google-auth) for more.
|
||||
- Sign in:
|
||||
- When a user signs in for the first time, Wasp will create a new User account and link it to their Google account for future logins. The `username` will default to a random dictionary phrase that does not exist in the database, like "nice-blue-horse-27160".
|
||||
- Aside: If you would like to allow the user to select their own username, or some other sign up flow, you could add a boolean property to your User entity which indicates if the account setup is complete. You can then redirect them in your `onAuthSucceededRedirectTo` handler.
|
||||
- Here is a link to the default implementations: https://github.com/wasp-lang/wasp/blob/main/waspc/data/Generator/templates/server/src/routes/auth/passport/google/googleDefaults.js These can be overriden as explained below.
|
||||
:::note Changing Random Username
|
||||
If you would like to allow the user to select their own username, or some other sign up flow, you could add a boolean property to your User entity which indicates if the account setup is complete. You can then check this user's property on the client with the [`useAuth()`](#useauth) hook and redirect them when appropriate -- e.g. check on homepage if `user.isAuthSetup === false`, redirect them to `EditUserDetailsPage`.
|
||||
|
||||
Alternatively, you could add a `displayName` property to your User entity and assign it using the details of their Google account, as described in **Overrides** below
|
||||
:::
|
||||
- Here is a link to the default implementations: https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/server/src/routes/auth/passport/google/googleDefaults.js . These can be overriden as explained below.
|
||||
|
||||
#### Overrides
|
||||
If you require modifications to the above, you can add one or more of the following to your `auth.methods.google` dictionary:
|
||||
|
32
web/docs/pick-a-tutorial.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Pick a Tutorial
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import DiscordLink from '../blog/components/DiscordLink';
|
||||
|
||||
Congrats, you're now familiar with the basic concepts of Wasp! 🥳
|
||||
|
||||
It's time to build something cool and check the capabilities of Wasp in action. Pick one of the tutorials below and let us know your impressions on <DiscordLink />!
|
||||
|
||||
### To-Do app
|
||||
|
||||
[The To-Do app](tutorials/todo-app) is a thorough journey covering most of the Wasp's concepts, from very basics to features like auth, dependency management, operations, ... . If you'd like to get familiar with Wasp on a more detailed level - this is a tutorial for you!
|
||||
|
||||
Time estimate: ~45 minutes
|
||||
|
||||
### Dev excuses app
|
||||
|
||||
[Dev excuses app](tutorials/dev-excuses-app) is a fun, quick overview of how you can build a full-stack app with Wasp in a matter of minutes. Do not expect any detailed concept explanations, and refer to this tutorial if you'd like to get a fast overview of Wasp's possibilities.
|
||||
|
||||
Time estimate: ~20 minutes
|
||||
|
||||
<hr/>
|
||||
|
||||
<img alt="Let's build!"
|
||||
src={useBaseUrl('img/develop-an-app.jpg')}
|
||||
style={{ border: "1px solid black" }}
|
||||
height="400px"
|
||||
/>
|
||||
|
||||
P.S: If you decided to build an app on your own - we'd love to see it! If it's simple enough, let's turn it into a tutorial! Please check our [contribution guide](contributing) for detailed instructions and reach us on Discord.
|
25
web/docs/tutorials/dev-excuses-app.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import DiscordLink from '../../blog/components/DiscordLink';
|
||||
|
||||
:::info
|
||||
Make sure you've set up Wasp! Check out [Getting Started](/getting-started.md) first for installation instructions, and then continue with the tutorial. In case of any issues - please, ping us on <DiscordLink />.
|
||||
:::
|
||||
|
||||
We’ll build a web app to solve every developer's most common problem – finding an excuse to justify our messy work! We will start with a single config file that outlines the full-stack architecture of our app plus several dozen lines of code for our specific business logic. There's no faster way to do it, so we can’t excuse ourselves from building it!
|
||||
|
||||
We’ll use Michele Gerarduzzi’s [open-source project](https://github.com/michelegera/devexcuses-api). It provides a simple API and a solid number of predefined excuses. A perfect fit for our needs. Let’s define the requirements for the project:
|
||||
|
||||
- The app must be able to pull excuses data from a public API.
|
||||
- Users can save the excuses they like (and your boss doesn't) to the database for future reference.
|
||||
- Building an app shouldn’t take more than 15 minutes.
|
||||
- Use modern web dev technologies (NodeJS + React)
|
||||
|
||||
As a result – we’ll get a simple and fun pet project. You can find the complete codebase [here](https://github.com/wasp-lang/wasp/tree/main/examples/tutorials/ItWaspsOnMyMachine).
|
||||
|
||||
<img alt="Final result"
|
||||
src={useBaseUrl('img/dev-excuses-live-preview.gif')}
|
||||
/>
|
@ -0,0 +1,28 @@
|
||||
---
|
||||
id: 01-creating-the-project
|
||||
title: Creating the project
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
By now you've already learned [how to install Wasp and create a new project](/getting-started.md). So let’s create a new web app appropriately named `ItWaspsOnMyMachine`.
|
||||
|
||||
```
|
||||
wasp new ItWaspsOnMyMachine
|
||||
```
|
||||
|
||||
Changing the working directory:
|
||||
```
|
||||
cd ItWaspsOnMyMachine
|
||||
```
|
||||
|
||||
Starting the app:
|
||||
```
|
||||
wasp start
|
||||
```
|
||||
|
||||
Now your default browser should open up with a simple predefined text message. That’s it! 🥳 For now – the codebase consists of only two files! `main.wasp` is the config file that defines the application’s functionality. And `MainPage.js` is the front-end. You can delete `Main.css`, we won't use that. And don't forget to remove `import './Main.css'` from `MainPage.js` file.
|
||||
|
||||
<img alt="Initial page"
|
||||
src={useBaseUrl('img/init-page.png')}
|
||||
/>
|
@ -0,0 +1,103 @@
|
||||
---
|
||||
id: 02-modifying-main-wasp-file
|
||||
title: Modifying main.wasp file
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
First and foremost, we need to add some dependencies and introduce operations to our project. We’ll add Tailwind to make our UI prettier and Axios for making API requests.
|
||||
|
||||
Also, we’ll declare a database entity called `Excuse`, queries, and action. The `Excuse` entity consists of the entity’s ID and the text.
|
||||
|
||||
`Queries` are here when we need to fetch/read something, while `actions` are here when we need to change/update data. Both query and action declaration consists of two lines – a reference to the file that contains implementation and a data model to operate on. You can find more info [in the docs section below](/docs/language/features#queries-and-actions-aka-operations). Let's move on!
|
||||
|
||||
Let's add the following code to the `main.wasp` file's `app` section:
|
||||
|
||||
```js title="main.wasp | Adding dependencies"
|
||||
head: [
|
||||
"<script src='https://cdn.tailwindcss.com'></script>"
|
||||
],
|
||||
|
||||
dependencies: [
|
||||
("axios", "^0.21.1")
|
||||
]
|
||||
```
|
||||
|
||||
Next, we'll add an Excuse entity to the bottom of the file. You'll also need to define queries and an action that operates on nit.
|
||||
|
||||
```js title="main.wasp | Defining Excuse entity, queries and action"
|
||||
entity Excuse {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
text String
|
||||
psl=}
|
||||
|
||||
query getExcuse {
|
||||
fn: import { getExcuse } from "@ext/queries.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
|
||||
query getAllSavedExcuses {
|
||||
fn: import { getAllSavedExcuses } from "@ext/queries.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
|
||||
action saveExcuse {
|
||||
fn: import { saveExcuse } from "@ext/actions.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
```
|
||||
The resulting `main.wasp` file should look like this:
|
||||
|
||||
```js title="main.wasp | Final result"
|
||||
|
||||
// Main declaration, defines a new web app.
|
||||
app ItWaspsOnMyMachine {
|
||||
|
||||
// Used as a browser tab title.
|
||||
title: "It Wasps On My Machine",
|
||||
|
||||
head: [
|
||||
// Adding Tailwind to make our UI prettier
|
||||
"<script src='https://cdn.tailwindcss.com'></script>"
|
||||
],
|
||||
|
||||
dependencies: [
|
||||
// Adding Axios for making HTTP requests
|
||||
("axios", "^0.21.1")
|
||||
]
|
||||
}
|
||||
|
||||
// Render page MainPage on url `/` (default url).
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
|
||||
// ReactJS implementation of our page located in `ext/MainPage.js` as a default export
|
||||
page MainPage {
|
||||
component: import Main from "@ext/MainPage.js"
|
||||
}
|
||||
|
||||
// Prisma database entity
|
||||
entity Excuse {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
text String
|
||||
psl=}
|
||||
|
||||
// Query declaration to get a new excuse
|
||||
query getExcuse {
|
||||
fn: import { getExcuse } from "@ext/queries.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
|
||||
// Query declaration to get all excuses
|
||||
query getAllSavedExcuses {
|
||||
fn: import { getAllSavedExcuses } from "@ext/queries.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
|
||||
// Action to save current excuse
|
||||
action saveExcuse {
|
||||
fn: import { saveExcuse } from "@ext/actions.js",
|
||||
entities: [Excuse]
|
||||
}
|
||||
```
|
||||
|
||||
Perfect! We've set up all the architecture of our app. Now let's add some logic.
|
35
web/docs/tutorials/dev-excuses-app/03-adding-operations.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
id: 03-adding-operations
|
||||
title: Adding operations
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
Now you'll need to create two files: `actions.js` and `queries.js` in the `ext` folder.
|
||||
|
||||
Let’s add `saveExcuse()` action to our `actions.js` file. This action will save the text of our excuse to the database.
|
||||
|
||||
```js title=".../ext/actions.js | Defining an action"
|
||||
export const saveExcuse = async (excuse, context) => {
|
||||
return context.entities.Excuse.create({
|
||||
data: { text: excuse.text }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then we need to create two queries in the `queries.js` file. First, one `getExcuse` will call an external API and fetch a new excuse. The second one, named `getAllSavedExcuses`, will pull all the excuses we’ve saved to our database.
|
||||
|
||||
```js title=".../ext/queries.js | Defining queries"
|
||||
import axios from 'axios';
|
||||
|
||||
export const getExcuse = async () => {
|
||||
const response = await axios.get('https://api.devexcus.es/')
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getAllSavedExcuses = async (_args, context) => {
|
||||
return context.entities.Excuse.findMany()
|
||||
}
|
||||
```
|
||||
|
||||
That’s it! We finished our back-end. 🎉 Now, let’s use those queries/actions on our UI.
|
@ -0,0 +1,74 @@
|
||||
---
|
||||
id: 04-updating-main-page-js-file
|
||||
title: Updating MainPage.js file
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
This is the most complex part, but it really comes down to mostly writing React. To make our life easier - let’s erase everything we had in the `MainPage.js` file and substitute it with our new UI markup.
|
||||
|
||||
```js title=".../ext/MainPage.js | Updating the UI"
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from '@wasp/queries'
|
||||
import getExcuse from '@wasp/queries/getExcuse'
|
||||
import getAllSavedExcuses from '@wasp/queries/getAllSavedExcuses'
|
||||
import saveExcuse from '@wasp/actions/saveExcuse'
|
||||
|
||||
const MainPage = () => {
|
||||
const [currentExcuse, setCurrentExcuse] = useState({ text: "" })
|
||||
const { data: excuses } = useQuery(getAllSavedExcuses)
|
||||
|
||||
const handleGetExcuse = async () => {
|
||||
try {
|
||||
setCurrentExcuse(await getExcuse())
|
||||
} catch (err) {
|
||||
window.alert('Error while getting the excuse: ' + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveExcuse = async () => {
|
||||
if (currentExcuse.text) {
|
||||
try {
|
||||
await saveExcuse(currentExcuse)
|
||||
} catch (err) {
|
||||
window.alert('Error while saving the excuse: ' + err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 text-3xl">
|
||||
<div>
|
||||
<button onClick={handleGetExcuse} className="mx-2 my-1 p-2 bg-blue-600 hover:bg-blue-400 text-white rounded"> Get excuse </button>
|
||||
<button onClick={handleSaveExcuse} className="mx-2 my-1 p-2 bg-blue-600 hover:bg-blue-400 text-white rounded"> Save excuse </button>
|
||||
<Excuse excuse={currentExcuse} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="px-6 py-2 bg-blue-600 text-white"> Saved excuses: </div>
|
||||
{excuses && <ExcuseList excuses={excuses} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ExcuseList = (props) => {
|
||||
return props.excuses?.length ? props.excuses.map((excuse, idx) => <Excuse excuse={excuse} key={idx} />) : 'No saved excuses'
|
||||
}
|
||||
|
||||
const Excuse = ({ excuse }) => {
|
||||
return (
|
||||
<div className="px-6 py-2">
|
||||
{excuse.text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainPage
|
||||
```
|
||||
|
||||
Our page consists of three components: `MainPage`, `ExcuseList`, and `Excuse`. It may seem at first that this file is pretty complex, but let's take a closer look:
|
||||
|
||||
`Excuse` is just a div with an excuse text, `ExcuseList` checks if there are any excuses. If the list is empty – show a message `No saved excuses`. In other case – excuses will be displayed.
|
||||
|
||||
`MainPage` contains info about the current excuses and the list of already saved excuses. Two button click handlers are `handleGetExcuse` and `handleSaveExcuse`. Plus, the markup itself with some Tailwind flavor.
|
||||
|
@ -0,0 +1,44 @@
|
||||
---
|
||||
id: 05-perform-migration-and-run
|
||||
title: Perform migration and run the app
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import DiscordLink from '../../../blog/components/DiscordLink';
|
||||
|
||||
Before we run our app, we need to execute a database migration. We changed the DB schema by adding new entities. By doing the migration, we sync the database schema with the entities we defined. If you’ve had something running in the terminal – stop it and run:
|
||||
|
||||
```
|
||||
wasp db migrate-dev
|
||||
```
|
||||
|
||||
You’ll be prompted to enter a name for the migration. Something like `init` will be ok. Now we can start the application!
|
||||
|
||||
```
|
||||
wasp start
|
||||
```
|
||||
<img alt="Final empty result"
|
||||
src={useBaseUrl('img/final-result.png')}
|
||||
/>
|
||||
|
||||
Now you can click the “Get excuse” button to receive an excuse. You should also be able to save the ones you like with the “Save excuse” button. Our final project should look like this:
|
||||
|
||||
<img alt="Final result"
|
||||
src={useBaseUrl('img/final-excuse-app.png')}
|
||||
/>
|
||||
|
||||
Now we can think of some additional improvements. For example:
|
||||
|
||||
- Add a unique constraint to Entity’s ID so we won’t be able to save duplicated excuses.
|
||||
- Add exceptions and edge cases handling.
|
||||
- Make the markup prettier.
|
||||
- Optimize and polish the code
|
||||
|
||||
So, we’ve been able to build a full-stack application with a database and external API call in a couple of minutes. And now we have a box full of excuses for all our development needs.
|
||||
|
||||
<img alt="Box of excuses for the win!"
|
||||
src={useBaseUrl('img/accessible-website-excuse.jpg')}
|
||||
/>
|
||||
|
||||
|
||||
P.S: now you're familiar with Wasp and can build full-stack apps, horaay! 🎉 How did it go? Was it fun? Drop us a message at our <DiscordLink />. Now it's time to look at [Todo App in Wasp](/docs/tutorials/todo-app) if you haven't already. It will introduce some additional concepts so you'd be able to become a true Wasp overlord!
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Creating a new project"
|
||||
id: 01-creating-new-project
|
||||
title: "Creating new project"
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 02-task-entity
|
||||
title: "Task entity"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 03-listing-tasks
|
||||
title: "Listing tasks"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 04-creating-tasks
|
||||
title: "Creating tasks"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 05-updating-tasks
|
||||
title: "Updating tasks"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 06-auth
|
||||
title: "Authentication"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 07-dependencies
|
||||
title: "Dependencies"
|
||||
---
|
||||
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
id: 08-the-end
|
||||
title: "The End"
|
||||
---
|
||||
|
@ -15,7 +15,16 @@ module.exports = {
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/favicon.ico',
|
||||
stylesheets: [
|
||||
'https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'
|
||||
],
|
||||
themeConfig: {
|
||||
announcementBar: {
|
||||
id: 'Beta_is_coming',
|
||||
content: '<strong>We are releasing Beta on Nov 27! 🚀 Sign up <a href="#signup">here</a> to get notified. 🔔</strong>',
|
||||
backgroundColor: '#ffcc00',
|
||||
isCloseable: false,
|
||||
},
|
||||
navbar: {
|
||||
title: '.wasp (beta)',
|
||||
logo: {
|
||||
@ -108,10 +117,6 @@ module.exports = {
|
||||
// it searches only in v1 docs if you are searching from v1 docs.
|
||||
// We should enable it if we start doing versioning.
|
||||
// contextualSearch: true
|
||||
},
|
||||
gtag: {
|
||||
trackingID: 'G-3ZEDH3BVGE',
|
||||
anonymizeIP: true,
|
||||
}
|
||||
},
|
||||
presets: [
|
||||
@ -148,5 +153,18 @@ module.exports = {
|
||||
scripts: [
|
||||
'/scripts/posthog.js',
|
||||
'/js/fix-multiple-trailing-slashes.js'
|
||||
]
|
||||
],
|
||||
plugins: [
|
||||
async function myPlugin(context, options) {
|
||||
return {
|
||||
name: "docusaurus-tailwindcss",
|
||||
configurePostCss(postcssOptions) {
|
||||
// Appends TailwindCSS and AutoPrefixer.
|
||||
postcssOptions.plugins.push(require("tailwindcss"));
|
||||
postcssOptions.plugins.push(require("autoprefixer"));
|
||||
return postcssOptions;
|
||||
},
|
||||
};
|
||||
},
|
||||
],
|
||||
};
|
||||
|
21048
web/package-lock.json
generated
@ -14,22 +14,29 @@
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-beta.6",
|
||||
"@docusaurus/plugin-google-gtag": "^2.0.0-beta.6",
|
||||
"@docusaurus/preset-classic": "2.0.0-beta.6",
|
||||
"@mdx-js/react": "^1.6.21",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"clsx": "^1.1.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"prism-react-renderer": "^1.2.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/plugin-google-gtag": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^1.2.1",
|
||||
"postcss": "^8.4.19",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-modal": "^3.14.3",
|
||||
"url-loader": "^4.1.1"
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16",
|
||||
"npm": "8"
|
||||
"node": "^18.12.0",
|
||||
"npm": "^8.19.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
@ -7,7 +7,8 @@ module.exports = {
|
||||
items: [
|
||||
'getting-started',
|
||||
'about',
|
||||
'how-it-works'
|
||||
'how-it-works',
|
||||
'pick-a-tutorial'
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -26,16 +27,29 @@ module.exports = {
|
||||
label: 'Basics',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'tutorials/todo-app/creating-new-project',
|
||||
'tutorials/todo-app/task-entity',
|
||||
'tutorials/todo-app/listing-tasks',
|
||||
'tutorials/todo-app/creating-tasks',
|
||||
'tutorials/todo-app/updating-tasks'
|
||||
'tutorials/todo-app/01-creating-new-project',
|
||||
'tutorials/todo-app/02-task-entity',
|
||||
'tutorials/todo-app/03-listing-tasks',
|
||||
'tutorials/todo-app/04-creating-tasks',
|
||||
'tutorials/todo-app/05-updating-tasks'
|
||||
]
|
||||
},
|
||||
'tutorials/todo-app/auth',
|
||||
'tutorials/todo-app/dependencies',
|
||||
'tutorials/todo-app/the-end'
|
||||
'tutorials/todo-app/06-auth',
|
||||
'tutorials/todo-app/07-dependencies',
|
||||
'tutorials/todo-app/08-the-end'
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Dev Excuses app',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'tutorials/dev-excuses-app',
|
||||
'tutorials/dev-excuses-app/01-creating-the-project',
|
||||
'tutorials/dev-excuses-app/02-modifying-main-wasp-file',
|
||||
'tutorials/dev-excuses-app/03-adding-operations',
|
||||
'tutorials/dev-excuses-app/04-updating-main-page-js-file',
|
||||
'tutorials/dev-excuses-app/05-perform-migration-and-run',
|
||||
]
|
||||
}
|
||||
]
|
||||
|
103
web/src/components/Benefits.js
Normal file
@ -0,0 +1,103 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { Terminal, Layers, Coffee, Code } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
import styles from '../pages/styles.module.css'
|
||||
|
||||
const Lang = () => (
|
||||
<>
|
||||
<span className='underline decoration-yellow-500 font-bold'>
|
||||
language
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
|
||||
const Benefit = ({ Icon, title, description }) => (
|
||||
<div className='mb-10 md:mb-0 space-y-4'>
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className={`
|
||||
inline-flex h-8 w-8 rounded-md
|
||||
items-center justify-center
|
||||
text-yellow-500 bg-neutral-700
|
||||
`}
|
||||
>
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
<dt className='ml-4'>
|
||||
{ title }
|
||||
</dt>
|
||||
</div>
|
||||
<p className=''>
|
||||
{ description }
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Benefits = () => {
|
||||
return (
|
||||
<SectionContainer className='space-y-16'>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
Yet another web framework. Except it's
|
||||
a <Lang />.
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
Don't worry, it takes less than 30 minutes to learn.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl className='grid grid-cols-1 lg:grid-cols-3 md:gap-16 lg:gap-x-8 xl:gap-x-24'>
|
||||
<Benefit
|
||||
Icon={Layers}
|
||||
title='Truly full-stack'
|
||||
description={`
|
||||
When we say full-stack, we really mean it. Wasp has you covered from front-end,
|
||||
back-end and database to deployment. Zero config required to get started.
|
||||
`}
|
||||
/>
|
||||
|
||||
<Benefit
|
||||
Icon={Coffee}
|
||||
title='The wheel can take a break'
|
||||
description={`
|
||||
No reinventing the wheel here. Write your code in React & Node.js as you are used to,
|
||||
along with your favourite NPM packages.
|
||||
`}
|
||||
/>
|
||||
|
||||
<Benefit
|
||||
Icon={Code}
|
||||
title='Less boilerplate'
|
||||
description={`
|
||||
The language approach allows us to immensely improve developer experience.
|
||||
E.g., full-stack auth takes only 5 lines of code.
|
||||
`}
|
||||
/>
|
||||
</dl>
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const BenefitsWithSkewedBorder = () => (
|
||||
<div className='relative'>
|
||||
<div className={classNames(styles.sectionSkewedContainer)}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.sectionSkewed,
|
||||
'border-t border-b border-yellow-500/25 bg-neutral-100/50'
|
||||
)}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<Benefits />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default BenefitsWithSkewedBorder
|
@ -1,12 +1,12 @@
|
||||
import React, { useState } from 'react'
|
||||
import CodeBlock from '@theme/CodeBlock'
|
||||
|
||||
export default function CodeBlockWithTitle ({ title, children, language, metastring }) {
|
||||
export default function CodeBlockWithTitle ({ title, children, language }) {
|
||||
return (
|
||||
<div className='code-with-header'>
|
||||
<div className="code-header">{ title }</div>
|
||||
|
||||
<CodeBlock className={language} metastring={metastring}>
|
||||
<CodeBlock language={language}>
|
||||
{ children }
|
||||
</CodeBlock>
|
||||
</div>
|
||||
|
44
web/src/components/DarkModeToggle.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Sun, Moon } from 'react-feather'
|
||||
|
||||
const DarkModeToggle = () => {
|
||||
const [isDarkMode, setIsDarkMode] = useState(false)
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(!isDarkMode)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-end'>
|
||||
<Sun strokeWidth={2} size={22} className='text-neutral-500' />
|
||||
<button
|
||||
type='button'
|
||||
aria-pressed='false'
|
||||
className={`
|
||||
relative inline-flex
|
||||
h-6 w-11 mx-3 flex-shrink-0 cursor-pointer
|
||||
rounded-full border-2 border-transparent
|
||||
bg-neutral-500
|
||||
transition-colors duration-200 ease-in-out focus:outline-none
|
||||
`}
|
||||
onClick={() => toggleDarkMode()}
|
||||
>
|
||||
<span
|
||||
aria-hidden='true'
|
||||
className={`
|
||||
${isDarkMode ? 'translate-x-5' : 'translate-x-0'}
|
||||
inline-block h-5 w-5
|
||||
bg-white shadow-lg rounded-full ring-0
|
||||
transform transition duration-200 ease-in-out
|
||||
`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<Moon strokeWidth={2} size={22} className='text-neutral-500' />
|
||||
</div>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default DarkModeToggle
|
126
web/src/components/Faq.js
Normal file
@ -0,0 +1,126 @@
|
||||
import React, { useState } from 'react'
|
||||
import Link from '@docusaurus/Link'
|
||||
import { ChevronDown, ChevronRight } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'How is Wasp different from Next.js / Nuxt.js / Gatsby?',
|
||||
answer: <p>
|
||||
<strong>TL;DR</strong> - These are frontend-first frameworks, with some limited backend capabilities.
|
||||
Wasp is a full-stack framework.
|
||||
<br/><br/>
|
||||
The main difference between Wasp and the solutions listed above is that Wasp is a trully full-stack
|
||||
framework, meaning it brings both back-end and database next to front-end. You can think of it as
|
||||
Ruby on Rails, but made for JS (React & Node.js) and full-stack.
|
||||
<br/><br/>
|
||||
Next.js, Gatsby and others started out as frontend frameworks for static sites. Although some of them
|
||||
now offer an option to use serverless functions, you still have to bring your own database and you'll
|
||||
also need some kind of a server/backend if you'll need to run more complex operations.
|
||||
</p>
|
||||
},
|
||||
{
|
||||
question: 'How is Wasp different from Ruby on Rails, Django, etc?',
|
||||
answer: <p>
|
||||
<strong>TL;DR</strong> - These are backend frameworks, while Wasp covers full-stack, which makes it easier to sync and
|
||||
reuse data models across the stack.
|
||||
<br/><br/>
|
||||
Back in the day when the whole UI was server-side rendered, Rails and Django were actually full-stack
|
||||
frameworks. But, with the advent of SPAs and frontend frameworks such as React, they are today mostly
|
||||
used as API servers, aka backend frameworks.
|
||||
<br/><br/>
|
||||
Wasp is a full-stack framework, which means it covers both frontend, backend and database at the same
|
||||
time. That allows us to make it super easy to work with and sync data models across the entire
|
||||
stack. You don't even have to define and deal with any kind of CRUD API - Wasp will do that for
|
||||
you under the hood.
|
||||
</p>
|
||||
},
|
||||
{
|
||||
question: 'How hard is it to learn Wasp?',
|
||||
answer: <p>
|
||||
We measured! <strong>It takes about 30 minutes to get going</strong>, and
|
||||
most users find it pretty straight-forward.
|
||||
Since the majority of your coding will still be done in the tools you're familiar with (currently
|
||||
React & Node.js), it's really a marginal change to what you're used to.
|
||||
<br/><br/>
|
||||
The reason is for that is that Wasp is a really simple configuration language, without any
|
||||
loops or variables - you can think of it as of a JSON that is easier to read and is a bit smarter.
|
||||
<br/><br/>
|
||||
Still, although simple (and we plan to keep it that way), it's a real language so you get all the
|
||||
IDE goodies with it - syntax highlighting, auto-completion, live error reporting, ...
|
||||
</p>
|
||||
},
|
||||
{
|
||||
question: 'Do you support only React & Node.js currently?',
|
||||
answer:<p>
|
||||
Yes, that is currently the supported stack. But, Wasp is being developed as a language/framework and
|
||||
architecture-agnostic tool, so we plan to add support for more languages and frameworks in the future.
|
||||
<br/><br/>
|
||||
This is something we're pretty excited about and think could be potentially be a unique opportunity
|
||||
due to the language approach we're taking with Wasp.
|
||||
</p>
|
||||
}
|
||||
]
|
||||
|
||||
const FaqItem = ({ keyP, faq }) => {
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<div className='py-6'>
|
||||
<dt key={keyP} className='text-base text-neutral-700'>
|
||||
<button
|
||||
className='text-left w-full flex items-center justify-between'
|
||||
onClick={() => { setIsExpanded(!isExpanded) }}
|
||||
>
|
||||
<span>{faq.question}</span>
|
||||
<div className='ml-6 text-yellow-500'>
|
||||
{isExpanded ? (
|
||||
<ChevronDown size={20} />
|
||||
) : (
|
||||
<ChevronRight size={20} />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</dt>
|
||||
{isExpanded && (
|
||||
<dd className='mt-2 text-neutral-500'>
|
||||
{faq.answer}
|
||||
</dd>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Faq = () => {
|
||||
return (
|
||||
<SectionContainer>
|
||||
<div className='grid grid-cols-12' id='faq'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
For anything not covered here, join
|
||||
<a
|
||||
href='https://discord.gg/rzdnErX'
|
||||
className='underline decoration-2 decoration-yellow-500 font-medium'
|
||||
>
|
||||
our Discord
|
||||
</a>!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl className='mt-6 max-w-3xl mx-auto divide-y divide-neutral-300'>
|
||||
{faqs.map((faq, idx) => (
|
||||
<FaqItem keyP={idx} key={idx} faq={faq} />
|
||||
))}
|
||||
</dl>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Faq
|
142
web/src/components/Footer.js
Normal file
@ -0,0 +1,142 @@
|
||||
import React from 'react'
|
||||
import Link from '@docusaurus/Link'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
import SubscribeForm from './SubscribeForm'
|
||||
import DarkModeToggle from './DarkModeToggle'
|
||||
|
||||
const docs = [
|
||||
{
|
||||
text: 'Getting Started',
|
||||
url: '/docs'
|
||||
},
|
||||
{
|
||||
text: 'Todo app tutorial',
|
||||
url: '/docs/tutorials/todo-app'
|
||||
},
|
||||
{
|
||||
text: 'Language reference',
|
||||
url: '/docs/language/overview'
|
||||
}
|
||||
]
|
||||
|
||||
const community = [
|
||||
{
|
||||
text: 'Discord',
|
||||
url: 'https://discord.gg/rzdnErX'
|
||||
},
|
||||
{
|
||||
text: 'Twitter',
|
||||
url: 'https://twitter.com/WaspLang'
|
||||
},
|
||||
{
|
||||
text: 'GitHub',
|
||||
url: 'https://github.com/wasp-lang/wasp'
|
||||
}
|
||||
]
|
||||
|
||||
const company = [
|
||||
{
|
||||
text: 'Blog',
|
||||
url: '/blog'
|
||||
},
|
||||
{
|
||||
text: 'Careers',
|
||||
url: 'https://www.notion.so/wasp-lang/Founding-Engineer-at-Wasp-88a73838f7f04ab3aee1f8e1c1bee6dd'
|
||||
},
|
||||
{
|
||||
text: 'Company',
|
||||
url: 'https://www.notion.so/wasp-lang/Founding-Engineer-at-Wasp-88a73838f7f04ab3aee1f8e1c1bee6dd#20569f14a8af452db10ae618d764d505'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO(matija): duplication, I already have Logo in Nav/index.js
|
||||
const Logo = () => (
|
||||
<div className='flex flex-shrink-0 items-center'>
|
||||
<Link to='/'>
|
||||
<img
|
||||
src='img/lp/wasp-logo.png'
|
||||
width={35}
|
||||
height={35}
|
||||
alt='Wasp Logo'
|
||||
/>
|
||||
</Link>
|
||||
<span className='ml-3 font-semibold text-lg text-neutral-700'>
|
||||
Wasp
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Segment = ({ title, links }) => (
|
||||
<div>
|
||||
<h6 className='text-neutral-700'>{title}</h6>
|
||||
<ul className='mt-4 space-y-2'>
|
||||
{links.map((l, idx) => {
|
||||
return (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={l.url}
|
||||
className={`
|
||||
text-sm text-neutral-500 hover:text-neutral-400
|
||||
transition-colors
|
||||
`}
|
||||
>
|
||||
{l.text}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Footer = () => {
|
||||
|
||||
return (
|
||||
<footer className='border-t'>
|
||||
<SectionContainer>
|
||||
<div className='grid grid-cols-1 gap-8 xl:grid xl:grid-cols-3'>
|
||||
|
||||
{/* cols with links */}
|
||||
<div className='grid grid-cols-1 xl:col-span-2'>
|
||||
<div className='grid grid-cols-2 gap-8 md:grid-cols-3'>
|
||||
|
||||
<Segment title='Docs' links={docs} />
|
||||
<Segment title='Community' links={community} />
|
||||
<Segment title='Company' links={company} />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* newsletter part */}
|
||||
<div className='xl:col-span-1'>
|
||||
<h3 className='text-base'>Stay up to date</h3>
|
||||
<p className='mt-4 text-sm text-neutral-500'>
|
||||
Join our mailing list and be the first to know when
|
||||
we ship new features and updates!
|
||||
</p>
|
||||
|
||||
<SubscribeForm
|
||||
className='mt-4 sm:flex sm:max-w-md'
|
||||
inputBgColor='bg-transparent'
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='pt-8 mt-8'>
|
||||
<Logo />
|
||||
<div className='flex justify-between'>
|
||||
<p className='mt-4 text-xs text-neutral-400'>
|
||||
© Wasp, Inc. All rights reserved.
|
||||
</p>
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
193
web/src/components/Hero.js
Normal file
@ -0,0 +1,193 @@
|
||||
import React from 'react'
|
||||
import Link from '@docusaurus/Link'
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter'
|
||||
import { qtcreatorLight, atomOneLight, atomOneDark, a11ylight } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
|
||||
import { Terminal } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const StartIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
||||
strokeLinejoin="round" opacity="0.5"
|
||||
>
|
||||
<polyline points="13 17 18 12 13 7"></polyline>
|
||||
<polyline points="6 17 11 12 6 7"></polyline>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const ActionButtons = () => (
|
||||
<div className='flex items-center gap-2'>
|
||||
<Link to='/docs'>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center space-x-2
|
||||
px-3 py-2 rounded
|
||||
bg-yellow-500 text-white text-sm leading-4
|
||||
border border-yellow-500 hover:border-yellow-400
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
<span>Get started in 5 minutes</span>
|
||||
<StartIcon />
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<Link to='#showcases'>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center space-x-2
|
||||
px-3 py-2 rounded
|
||||
border border-neutral-500
|
||||
text-sm leading-4
|
||||
hover:text-neutral-400 hover:border-neutral-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
<Terminal size={16} />
|
||||
<span>Showcases</span>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
const PHBadge = () => (
|
||||
<a
|
||||
href="https://www.producthunt.com/posts/wasp-lang-alpha?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-wasp-lang-alpha"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
className='w-32 md:w-[180px]'
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=277135&theme=light&period=daily"
|
||||
alt="Wasp-lang Alpha - Develop web apps in React & Node.js with no boilerplate | Product Hunt"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
|
||||
const Hero = () => {
|
||||
const codeString =
|
||||
`app todoApp {
|
||||
title: "ToDo App", /* visible in tab */
|
||||
|
||||
auth: { /* full-stack auth out-of-the-box */
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
/* import your React code */
|
||||
component: import Main from "@ext/Main.js"
|
||||
}`
|
||||
|
||||
return (
|
||||
<SectionContainer className='pb-5 pt-24'>
|
||||
<div className='lg:grid lg:grid-cols-12 lg:gap-16'>
|
||||
|
||||
<div className='lg:col-span-6 space-y-12'>
|
||||
{/* Hero title and subtitle */}
|
||||
<div>
|
||||
<h1
|
||||
className={`
|
||||
text-4xl lg:text-5xl lg:leading-tight
|
||||
font-extrabold text-neutral-700
|
||||
`}
|
||||
>
|
||||
Develop full-stack web apps
|
||||
<span className='underline decoration-yellow-500'>without boilerplate</span>.
|
||||
</h1>
|
||||
|
||||
<p className='mt-4 sm:mt-5 text-xl lg:text-xl text-neutral-500'>
|
||||
Describe common features via Wasp DSL and write the rest in React, Node.js
|
||||
and Prisma.
|
||||
</p>
|
||||
</div> {/* EOF Hero title and subtitle */}
|
||||
|
||||
<ActionButtons />
|
||||
|
||||
<div className='flex flex-col gap-4'>
|
||||
<small className='text-neutral-500 text-xs'>works with</small>
|
||||
|
||||
<div className='flex'>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='img/lp/react-logo-gray.svg'
|
||||
alt='React'
|
||||
/>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='img/lp/nodejs-logo-gray.svg'
|
||||
alt='Node'
|
||||
/>
|
||||
<img
|
||||
className='h-8 md:h-10 pr-5 md:pr-10'
|
||||
src='img/lp/prisma-logo-gray.svg'
|
||||
alt='Prisma'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='lg:col-span-6 lg:mt-0 mt-16'>
|
||||
<div className='relative flex flex-col items-center justify-center'>
|
||||
|
||||
<div className='bg-yellow-500/10 flex h-6 w-full items-center justify-between rounded-t-md px-2'>
|
||||
<span className='text-sm text-neutral-500'>todoApp.wasp</span>
|
||||
<div className='flex space-x-2'>
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
<div className='bg-yellow-500 h-2 w-2 rounded-full' />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='w-full text-sm shadow-2xl rounded-b-md'>
|
||||
<SyntaxHighlighter
|
||||
language="javascript"
|
||||
style={atomOneLight}
|
||||
customStyle={{
|
||||
borderBottomLeftRadius: '10px',
|
||||
borderBottomRightRadius: '10px',
|
||||
paddingLeft: '15px',
|
||||
}}
|
||||
>
|
||||
{codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div> {/* EOF code block wrapper */}
|
||||
|
||||
</div> {/* EOF wrapper of header + code */}
|
||||
|
||||
</div> {/* EOF col-span-6 */}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='flex justify-center items-center space-x-4 mt-20 mb-10 md:mt-28 md:mb-0'>
|
||||
<PHBadge />
|
||||
<div
|
||||
className={`
|
||||
h-11 border border-transparent border-l-neutral-400/50
|
||||
`}
|
||||
/>
|
||||
<img
|
||||
className='w-32 md:w-[180px]'
|
||||
src='img/lp/yc-logo.png'
|
||||
alt='YC'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default Hero
|
107
web/src/components/HowItWorks.js
Normal file
@ -0,0 +1,107 @@
|
||||
import React from 'react'
|
||||
import Link from '@docusaurus/Link'
|
||||
import { ArrowRight } from 'react-feather'
|
||||
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const Feature = ({ title, description, url }) => (
|
||||
<div className='col-span-12 md:col-span-6'>
|
||||
<div className='lg:mt-5'>
|
||||
<dt>
|
||||
<h4 className='mb-4'>
|
||||
<span className='bg-yellow-500/25 px-2 py-1 rounded'>{ title }</span>
|
||||
</h4>
|
||||
<p className='text-neutral-600'>
|
||||
{ description }
|
||||
</p>
|
||||
<TextLink url={url} label='Learn more' />
|
||||
</dt>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const TextLink = ({ url, label }) => (
|
||||
<Link to={url}>
|
||||
<span
|
||||
className={`
|
||||
mt-3 block cursor-pointer text-sm
|
||||
text-neutral-600 hover:text-neutral-500
|
||||
`}
|
||||
>
|
||||
<div className='group flex gap-1 items-center'>
|
||||
<span>{label}</span>
|
||||
<div className='transition-all group-hover:ml-0.5'>
|
||||
<span className='text-yellow-600'>
|
||||
<ArrowRight size={14} strokeWidth={2} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
const HowItWorks = () => {
|
||||
return (
|
||||
<SectionContainer className='lg:pb-8'>
|
||||
<div className='grid grid-cols-12'>
|
||||
|
||||
<div className='col-span-12 lg:col-span-4'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
What's under the hood? 🚕
|
||||
</h2>
|
||||
<p className='text-neutral-700'>
|
||||
Given <code>.wasp</code> and <code>.js(x)/.css/...</code>, source files,
|
||||
Wasp compiler generates the full source of your web app in
|
||||
the target stack - front-end, back-end and deployment.
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<div className='py-8'>
|
||||
<dl className='grid grid-cols-12 gap-y-4 md:gap-8'>
|
||||
|
||||
<Feature
|
||||
title='Typescript support'
|
||||
url='/'
|
||||
description="JS or TS - mix'n'match as you wish."
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='Wasp CLI'
|
||||
url='/docs/cli'
|
||||
description='All the handy commands at your fingertips.'
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='LSP for VS Code'
|
||||
url=''
|
||||
description='Syntax highligthing, go-to-definition, etc. work out-of-the-box.'
|
||||
/>
|
||||
|
||||
<Feature
|
||||
title='Deploy anywhere'
|
||||
url='/docs/deploying'
|
||||
description='See our deployment guide for more details.'
|
||||
/>
|
||||
|
||||
</dl>
|
||||
|
||||
</div> {/* EOF Features */}
|
||||
</div>
|
||||
|
||||
<div className='col-span-12 lg:col-span-7 xl:col-span-7 xl:col-start-6'>
|
||||
<img
|
||||
className=''
|
||||
src='img/lp/wasp-compilation-diagram.png'
|
||||
alt='React'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default HowItWorks
|
18
web/src/components/Layouts/SectionContainer.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const SectionContainer = ({ children, className, id }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'container mx-auto px-6 py-16 sm:py-18',
|
||||
'md:py-24',
|
||||
'lg:px-16 lg:py-24 xl:px-20',
|
||||
className
|
||||
)}
|
||||
id={id}
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
export default SectionContainer
|
15
web/src/components/Nav/SocialIcons.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
export const DiscordIcon = () => (
|
||||
<svg width='24' height='24' fill='currentColor' viewBox='0 5 30.67 23.25'>
|
||||
<title>Discord</title>
|
||||
<path d='M26.0015 6.9529C24.0021 6.03845 21.8787 5.37198 19.6623 5C19.3833 5.48048 19.0733 6.13144 18.8563 6.64292C16.4989 6.30193 14.1585 6.30193 11.8336 6.64292C11.6166 6.13144 11.2911 5.48048 11.0276 5C8.79575 5.37198 6.67235 6.03845 4.6869 6.9529C0.672601 12.8736 -0.41235 18.6548 0.130124 24.3585C2.79599 26.2959 5.36889 27.4739 7.89682 28.2489C8.51679 27.4119 9.07477 26.5129 9.55525 25.5675C8.64079 25.2265 7.77283 24.808 6.93587 24.312C7.15286 24.1571 7.36986 23.9866 7.57135 23.8161C12.6241 26.1255 18.0969 26.1255 23.0876 23.8161C23.3046 23.9866 23.5061 24.1571 23.7231 24.312C22.8861 24.808 22.0182 25.2265 21.1037 25.5675C21.5842 26.5129 22.1422 27.4119 22.7621 28.2489C25.2885 27.4739 27.8769 26.2959 30.5288 24.3585C31.1952 17.7559 29.4733 12.0212 26.0015 6.9529ZM10.2527 20.8402C8.73376 20.8402 7.49382 19.4608 7.49382 17.7714C7.49382 16.082 8.70276 14.7025 10.2527 14.7025C11.7871 14.7025 13.0425 16.082 13.0115 17.7714C13.0115 19.4608 11.7871 20.8402 10.2527 20.8402ZM20.4373 20.8402C18.9183 20.8402 17.6768 19.4608 17.6768 17.7714C17.6768 16.082 18.8873 14.7025 20.4373 14.7025C21.9717 14.7025 23.2271 16.082 23.1961 17.7714C23.1961 19.4608 21.9872 20.8402 20.4373 20.8402Z'></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TwitterIcon = () => (
|
||||
<svg width='24' height='24' fill='currentColor' viewBox='0 0 335 276'>
|
||||
<title>Twitter</title>
|
||||
<path d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'></path>
|
||||
</svg>
|
||||
)
|
216
web/src/components/Nav/index.js
Normal file
@ -0,0 +1,216 @@
|
||||
import React from 'react'
|
||||
import Link from '@docusaurus/Link'
|
||||
import { Star, Twitter } from 'react-feather'
|
||||
|
||||
import { DiscordIcon, TwitterIcon } from './SocialIcons'
|
||||
|
||||
const Nav = () => {
|
||||
|
||||
const Logo = () => (
|
||||
<div className='flex flex-shrink-0 items-center'>
|
||||
<Link to='/'>
|
||||
<img
|
||||
src='img/lp/wasp-logo.png'
|
||||
width={35}
|
||||
height={35}
|
||||
alt='Wasp Logo'
|
||||
/>
|
||||
</Link>
|
||||
<span className='ml-3 font-semibold text-lg text-neutral-700'>
|
||||
Wasp <sup className='text-base text-yellow-500'>βeta</sup>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const SocialIcon = ({ Icon, url }) => (
|
||||
<a href={url} target='_blank' rel='noreferrer'
|
||||
className={`
|
||||
hidden lg:flex hover:opacity-75 py-5
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
`}
|
||||
>
|
||||
<Icon />
|
||||
</a>
|
||||
)
|
||||
|
||||
const GitHubButton = () => (
|
||||
<a href='https://github.com/wasp-lang/wasp' target='_blank' rel='noreferrer'
|
||||
className={`
|
||||
px-2.5 py-1 rounded
|
||||
hover:bg-neutral-200
|
||||
transition ease-out duration-200
|
||||
hidden lg:flex items-center space-x-2 text-xs
|
||||
group
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex h-3 w-3 items-center justify-center
|
||||
group-hover:h-4 group-hover:w-4
|
||||
group-hover:text-yellow-500
|
||||
`}
|
||||
>
|
||||
<Star strokeWidth={2} />
|
||||
</div>
|
||||
<span className='truncate'>Star us on GitHub</span>
|
||||
</a>
|
||||
)
|
||||
|
||||
const HamburgerButton = () => (
|
||||
<div className='absolute inset-y-0 left-0 px-2 flex items-center lg:hidden'>
|
||||
<button
|
||||
className={`
|
||||
inline-flex items-center p-2
|
||||
rounded-md hover:bg-gray-50
|
||||
focus:ring-yellow-500 focus:outline-none focus:ring-2 focus:ring-inset
|
||||
`}
|
||||
>
|
||||
|
||||
<span className="sr-only">Open menu</span>
|
||||
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='sticky top-0 z-50'>
|
||||
<div className='bg-[#f5f4f0] absolute top-0 h-full w-full opacity-80'></div>
|
||||
<nav className='border-b backdrop-blur-sm'>
|
||||
<div className='
|
||||
relative mx-auto
|
||||
flex justify-between
|
||||
h-16
|
||||
lg:container lg:px-16 xl:px-20
|
||||
'
|
||||
>
|
||||
<HamburgerButton />
|
||||
<div className='
|
||||
flex flex-1
|
||||
items-center justify-center
|
||||
sm:items-stretch
|
||||
lg:justify-between
|
||||
'
|
||||
>
|
||||
<div className='flex items-center'> {/* Navbar left side */}
|
||||
<Logo />
|
||||
|
||||
<div className='hidden pl-4 sm:ml-6 sm:space-x-4 lg:flex'> {/* Left items */}
|
||||
{/* Docs */}
|
||||
<Link to='/docs'>
|
||||
<span
|
||||
className={`
|
||||
py-5 px-1
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-solid border-b-2 border-transparent
|
||||
text-sm font-semibold
|
||||
`}
|
||||
>
|
||||
Docs
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Blog */}
|
||||
<Link to='/blog'>
|
||||
<span
|
||||
className={`
|
||||
py-5 px-1
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
text-sm font-semibold
|
||||
`}
|
||||
>
|
||||
Blog
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* FAQ */}
|
||||
<Link to='#faq'>
|
||||
<span
|
||||
className={`
|
||||
py-5 px-1
|
||||
hover:text-yellow-500 hover:border-yellow-500
|
||||
border-b-2 border-transparent
|
||||
text-sm font-medium
|
||||
`}
|
||||
>
|
||||
FAQ
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Join newsletter */}
|
||||
<Link to='#signup'>
|
||||
<span
|
||||
className={`
|
||||
py-5 px-1
|
||||
border-b-2 border-transparent
|
||||
text-sm font-medium
|
||||
`}
|
||||
>
|
||||
<span className='px-2 py-1 rounded bg-yellow-500/25 hover:bg-yellow-500/10'>
|
||||
📬 Join the list
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
</div> {/* EOF left items */}
|
||||
</div> {/* EOF left side */}
|
||||
|
||||
<div className='flex items-center gap-2 space-x-2'> {/* Navbar right side */}
|
||||
<GitHubButton />
|
||||
|
||||
<Link to='/docs'>
|
||||
<button
|
||||
className={`
|
||||
hidden lg:block text-xs
|
||||
px-2.5 py-1 rounded
|
||||
bg-yellow-500 text-white
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Get started
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<SocialIcon
|
||||
Icon={ DiscordIcon }
|
||||
url='https://discord.gg/rzdnErX'
|
||||
/>
|
||||
|
||||
<SocialIcon
|
||||
Icon={ TwitterIcon }
|
||||
url='https://twitter.com/WaspLang'
|
||||
/>
|
||||
</div> {/* EOF right side */}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default Nav
|
46
web/src/components/Newsletter.js
Normal file
@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
|
||||
import SubscribeForm from './SubscribeForm'
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const Newsletter = () => {
|
||||
return (
|
||||
<SectionContainer id='signup'>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12'>
|
||||
|
||||
<div
|
||||
className={`
|
||||
px-6 py-6 bg-yellow-500/25 rounded-lg
|
||||
md:p-12 lg:p-16
|
||||
xl:flex xl:items-center
|
||||
`}
|
||||
>
|
||||
<div className='xl:w-0 xl:flex-1'>
|
||||
<h2 className='text-2xl font-extrabold text-neutral-700'>
|
||||
Stay up to date
|
||||
</h2>
|
||||
<p className='mt-3 text-lg text-neutral-500 leading-6'>
|
||||
Be the first to know when we ship new features and updates!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 sm:w-full sm:max-w-md xl:mt-0 xl:ml-8'>
|
||||
<SubscribeForm
|
||||
className='sm:flex'
|
||||
inputBgColor='bg-[#f5f5f5]'
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Newsletter
|
||||
|
106
web/src/components/ShowcaseGallery.js
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import SectionContainer from './Layouts/SectionContainer'
|
||||
|
||||
const Tag = ({ text, className }) => (
|
||||
<span
|
||||
className={classNames(`
|
||||
text-sm border rounded-md px-2.5 py-0.5
|
||||
|
||||
`, className
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
|
||||
const ShowcaseItem = ({ url, thumb, title, description, children }) => (
|
||||
<div>
|
||||
<a href={url}>
|
||||
<div className='group inline-block min-w-full'>
|
||||
<div className='flex flex-col space-y-3 pb-8 md:pb-0'>
|
||||
|
||||
<div
|
||||
className={`
|
||||
border-neutral-300 relative mb-4 h-60 w-full overflow-auto
|
||||
rounded-lg border shadow-lg overflow-y-hidden
|
||||
`}
|
||||
>
|
||||
<img
|
||||
src={thumb}
|
||||
className='object-cover'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 className='text-neutral-700 text-xl'>{title}</h3>
|
||||
|
||||
<div className='flex space-x-2'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<p className='text-neutral-500 text-base'>{description}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
const ShowcaseGallery = () => {
|
||||
return (
|
||||
<SectionContainer className='space-y-16' id='showcases'>
|
||||
<div className='grid grid-cols-12'>
|
||||
<div className='col-span-12 text-center'>
|
||||
<h2 className='text-xl lg:text-2xl text-neutral-700 mb-4'>
|
||||
🏆 Showcase Gallery 🏆
|
||||
</h2>
|
||||
<p className='text-neutral-500'>
|
||||
See what others are building with Wasp.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
mx-auto grid max-w-lg gap-8
|
||||
lg:max-w-none lg:grid-cols-3 lg:gap-12
|
||||
`}
|
||||
>
|
||||
<ShowcaseItem
|
||||
url='/blog/2022/10/28/farnance-hackathon-winner'
|
||||
thumb='img/lp/showcase/farnance-dashboard.png'
|
||||
title='Farnance: SaaS marketplace for farmers'
|
||||
description="See how Julian won HackLBS 2021 among 250 participants and why Wasp was instrumental for the team's victory."
|
||||
>
|
||||
<Tag text='hackathon' className='text-yellow-600 border-yellow-600 bg-yellow-50' />
|
||||
<Tag text='material-ui' className='text-blue-500 border-blue-500 bg-slate-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
<ShowcaseItem
|
||||
url='/blog/2022/11/26/michael-curry-usecase'
|
||||
thumb='img/lp/showcase/grabbit-hero.png'
|
||||
title='Grabbit: Easily manage dev environments'
|
||||
description='See how Michael built and deployed an internal tool for managing dev resources at StudentBeans.'
|
||||
>
|
||||
<Tag text='internal-tools' className='text-green-600 border-green-600 bg-green-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
<ShowcaseItem
|
||||
url='/blog/2022/11/26/erlis-amicus-usecase'
|
||||
thumb='img/lp/showcase/amicus-landing.png'
|
||||
title='Amicus: Task and workflow management for legal teams'
|
||||
description='See how Erlis rolled out fully-fledged SaaS as a team of one in record time and got first paying customers.'
|
||||
>
|
||||
<Tag text='startup' className='text-fuchsia-600 border-fuchsia-600 bg-fuchsia-50' />
|
||||
<Tag text='material-ui' className='text-blue-500 border-blue-500 bg-slate-50' />
|
||||
</ShowcaseItem>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowcaseGallery
|
44
web/src/components/SubscribeForm.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const SubscribeForm = ({ className, inputBgColor }) => (
|
||||
<form
|
||||
className={classNames('', className)}
|
||||
action="https://gmail.us4.list-manage.com/subscribe/post?u=8139c7de74df98aa17054b235&id=f0c6ba5f1d"
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
aria-label="Email address"
|
||||
type="email"
|
||||
name="EMAIL"
|
||||
id="email-address"
|
||||
required autoComplete='email'
|
||||
placeholder='you@awesomedev.com'
|
||||
className={`
|
||||
text-sm w-full
|
||||
appearance-none
|
||||
placeholder:text-neutral-400
|
||||
border border-yellow-500
|
||||
px-4 py-2 rounded-md
|
||||
focus:outline-none focus:ring-2 focus:ring-yellow-400
|
||||
` + ` ${inputBgColor}`}
|
||||
/>
|
||||
<div className='rounded-md mt-3 sm:mt-0 sm:ml-3'>
|
||||
<button
|
||||
type='submit'
|
||||
className={`
|
||||
w-full
|
||||
text-sm border border-transparent rounded-md
|
||||
bg-yellow-500 text-white
|
||||
px-4 py-2
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default SubscribeForm
|
@ -5,6 +5,26 @@
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
@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;
|
||||
|
||||
/* Removes blue tap box over Button/Link on mobile.
|
||||
* Issue on Tailwind's repo: https://github.com/tailwindlabs/tailwindcss/discussions/2984
|
||||
*/
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-smoothing: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Docusaurus stuff */
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
/* Our custom values */
|
||||
@ -18,6 +38,7 @@
|
||||
--ifm-h3-font-size: 1.3rem;
|
||||
|
||||
/* Infima overrides */
|
||||
--ifm-font-family-base: 'Inter';
|
||||
--ifm-color-primary: #BF9900; /* wasp color (ffcc00) darkened by 25% */
|
||||
--ifm-color-primary-dark: #8a6f04;
|
||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
||||
@ -47,305 +68,3 @@
|
||||
--custom-background-color-diff: #2A2A2A;
|
||||
}
|
||||
|
||||
/* In-blog stuff */
|
||||
|
||||
/* NOTE(matija): this is used within a blog post (e.g. seed round
|
||||
* announcement). */
|
||||
|
||||
.in-blog-cta-link-container {
|
||||
border: solid var(--ifm-link-color);
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.in-blog-cta--link {
|
||||
color: var(--ifm-color-primary);
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.in-blog-cta--divider {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.image-caption {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* EOF In-blog stuff */
|
||||
|
||||
.button--huge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.5rem;
|
||||
height: 64px;
|
||||
|
||||
/* --ifm-button-size-multiplier: 1.95; */
|
||||
}
|
||||
|
||||
.modal-step-title {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.emailCtaTop {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.hero-row {
|
||||
margin-bottom: 9rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2.2rem; /* 2.7rem */
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 2.5rem;
|
||||
line-height: 2.7rem;
|
||||
font-weight: 500;
|
||||
|
||||
margin-bottom: 3.5rem;
|
||||
}
|
||||
|
||||
.hero-text-col {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-works-with-icons img {
|
||||
max-height: 4rem;
|
||||
}
|
||||
|
||||
.hero-works-with-icons img:not(:last-child) {
|
||||
margin-right: 2.2rem;
|
||||
}
|
||||
|
||||
.works-with-text {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.bg-diff {
|
||||
background-color: var(--custom-background-color-diff);
|
||||
}
|
||||
|
||||
.prism-code {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.in-blog-cta--link {display: block;}
|
||||
.in-blog-cta--divider{display: none;}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.row .col {
|
||||
padding-top: var(--ifm-col-spacing-vertical);
|
||||
padding-bottom: var(--ifm-col-spacing-vertical);
|
||||
}
|
||||
}
|
||||
.docusaurus-highlight-code-line {
|
||||
background-color: rgb(0, 77, 91);
|
||||
display: block;
|
||||
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||
padding: 0 var(--ifm-pre-padding);
|
||||
}
|
||||
|
||||
#signup-atf {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#signup {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
.section-text {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.token.command {
|
||||
color: white
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.55rem;
|
||||
}
|
||||
|
||||
.title-strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.codeExampleFiles > *:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.code-with-header pre {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
background: var(--ifm-code-background-dark);
|
||||
color: #ccc;
|
||||
padding: 5px 15px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
/* text-transform: uppercase; */
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
img.logo {
|
||||
max-height: 2rem;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] img.logo {
|
||||
filter: invert(1)
|
||||
}
|
||||
|
||||
img.wasp-diagram {
|
||||
max-height: 450px;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
img.rwa {
|
||||
border-style: solid;
|
||||
border-width: 5px;
|
||||
border-radius: 5px;
|
||||
border-color: var(--ifm-color-primary);
|
||||
margin-bottom: 1rem;
|
||||
padding: 1px;
|
||||
}
|
||||
img.rwa:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
img.wasp-diagram {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.section-lg {
|
||||
margin: 5rem 0;
|
||||
}
|
||||
|
||||
.CodeExamples {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.ButtonTabs {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.ButtonTabs .button {
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 10px;
|
||||
border: 2px solid var(--ifm-button-background-color);
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ButtonTabs .button.is-active,
|
||||
.ButtonTabs .button:hover {
|
||||
border: 2px solid var(--ifm-color-primary);
|
||||
box-shadow: var(--custom-shadow-lw);
|
||||
}
|
||||
|
||||
/* But */
|
||||
@media screen and (max-width: 997px) {
|
||||
.ButtonTabs {
|
||||
padding-left: var(--ifm-spacing-horizontal) !important;
|
||||
}
|
||||
.ButtonTabs > div {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
overflow-x: auto !important;
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar !important;
|
||||
}
|
||||
.ButtonTabs .button {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-item-inner {
|
||||
font-weight: var(--ifm-font-weight-normal)
|
||||
}
|
||||
|
||||
/* Discord icon */
|
||||
|
||||
.navbar-item-discord {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.navbar-item-discord:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.navbar-item-discord:before {
|
||||
content: '';
|
||||
margin-top: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 3 36 36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M29.9699 7.7544C27.1043 5.44752 22.5705 5.05656 22.3761 5.04288C22.2284 5.03072 22.0806 5.0648 21.9531 5.1404C21.8257 5.216 21.7249 5.32937 21.6647 5.4648C21.5783 5.65936 21.5049 5.85949 21.4451 6.06384C23.3409 6.38424 25.6694 7.02864 27.7761 8.33616C27.8565 8.38604 27.9262 8.45126 27.9814 8.52809C28.0366 8.60493 28.0761 8.69187 28.0976 8.78397C28.1192 8.87607 28.1224 8.97151 28.1071 9.06485C28.0917 9.15819 28.0582 9.24759 28.0083 9.32796C27.9584 9.40833 27.8932 9.47809 27.8164 9.53325C27.7395 9.58842 27.6526 9.62791 27.5605 9.64947C27.4684 9.67103 27.373 9.67424 27.2796 9.65892C27.1863 9.6436 27.0969 9.61004 27.0165 9.56016C23.3949 7.3116 18.8719 7.2 17.9999 7.2C17.1287 7.2 12.6028 7.31232 8.98338 9.55944C8.90301 9.60932 8.81361 9.64288 8.72027 9.6582C8.62693 9.67352 8.53149 9.67031 8.43939 9.64875C8.25339 9.6052 8.09231 9.48955 7.99158 9.32724C7.89085 9.16493 7.85873 8.96925 7.90227 8.78325C7.94582 8.59725 8.06147 8.43617 8.22378 8.33544C10.3305 7.03152 12.659 6.38424 14.5547 6.06672C14.4453 5.7096 14.3459 5.48424 14.3387 5.4648C14.2788 5.32841 14.1776 5.2143 14.0493 5.13859C13.921 5.06288 13.7721 5.0294 13.6238 5.04288C13.4294 5.05728 8.89554 5.44752 5.99034 7.78536C4.47474 9.18792 1.43994 17.3894 1.43994 24.48C1.43994 24.6067 1.47378 24.7277 1.5357 24.8371C3.62802 28.5163 9.3405 29.4775 10.6423 29.52H10.6646C10.7782 29.5203 10.8903 29.4937 10.9916 29.4424C11.093 29.3911 11.1808 29.3165 11.2478 29.2248L12.5632 27.4133C9.01146 26.4967 7.19706 24.9386 7.09338 24.8458C6.95017 24.7194 6.86303 24.5412 6.85115 24.3506C6.83927 24.1599 6.90361 23.9723 7.03002 23.8291C7.15643 23.6859 7.33456 23.5988 7.52522 23.5869C7.71588 23.575 7.90345 23.6394 8.04666 23.7658C8.08842 23.8054 11.4299 26.64 17.9999 26.64C24.5807 26.64 27.9223 23.7938 27.9561 23.7658C28.0998 23.6403 28.2874 23.5769 28.4777 23.5896C28.668 23.6023 28.8456 23.69 28.9713 23.8334C29.0335 23.9042 29.0812 23.9864 29.1117 24.0756C29.1421 24.1647 29.1546 24.259 29.1486 24.353C29.1426 24.447 29.1181 24.5389 29.0766 24.6235C29.035 24.708 28.9772 24.7836 28.9065 24.8458C28.8028 24.9386 26.9884 26.4967 23.4367 27.4133L24.7528 29.2248C24.8198 29.3164 24.9074 29.3909 25.0087 29.4422C25.1099 29.4935 25.2218 29.5202 25.3353 29.52H25.3569C26.6601 29.4775 32.3719 28.5156 34.4649 24.8371C34.5261 24.7277 34.5599 24.6067 34.5599 24.48C34.5599 17.3894 31.5251 9.18864 29.9699 7.7544V7.7544ZM13.3199 21.6C11.9275 21.6 10.7999 20.3112 10.7999 18.72C10.7999 17.1288 11.9275 15.84 13.3199 15.84C14.7124 15.84 15.8399 17.1288 15.8399 18.72C15.8399 20.3112 14.7124 21.6 13.3199 21.6ZM22.6799 21.6C21.2875 21.6 20.1599 20.3112 20.1599 18.72C20.1599 17.1288 21.2875 15.84 22.6799 15.84C24.0724 15.84 25.1999 17.1288 25.1999 18.72C25.1999 20.3112 24.0724 21.6 22.6799 21.6Z'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .navbar-item-discord:before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 3 36 36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M29.9699 7.7544C27.1043 5.44752 22.5705 5.05656 22.3761 5.04288C22.2284 5.03072 22.0806 5.0648 21.9531 5.1404C21.8257 5.216 21.7249 5.32937 21.6647 5.4648C21.5783 5.65936 21.5049 5.85949 21.4451 6.06384C23.3409 6.38424 25.6694 7.02864 27.7761 8.33616C27.8565 8.38604 27.9262 8.45126 27.9814 8.52809C28.0366 8.60493 28.0761 8.69187 28.0976 8.78397C28.1192 8.87607 28.1224 8.97151 28.1071 9.06485C28.0917 9.15819 28.0582 9.24759 28.0083 9.32796C27.9584 9.40833 27.8932 9.47809 27.8164 9.53325C27.7395 9.58842 27.6526 9.62791 27.5605 9.64947C27.4684 9.67103 27.373 9.67424 27.2796 9.65892C27.1863 9.6436 27.0969 9.61004 27.0165 9.56016C23.3949 7.3116 18.8719 7.2 17.9999 7.2C17.1287 7.2 12.6028 7.31232 8.98338 9.55944C8.90301 9.60932 8.81361 9.64288 8.72027 9.6582C8.62693 9.67352 8.53149 9.67031 8.43939 9.64875C8.25339 9.6052 8.09231 9.48955 7.99158 9.32724C7.89085 9.16493 7.85873 8.96925 7.90227 8.78325C7.94582 8.59725 8.06147 8.43617 8.22378 8.33544C10.3305 7.03152 12.659 6.38424 14.5547 6.06672C14.4453 5.7096 14.3459 5.48424 14.3387 5.4648C14.2788 5.32841 14.1776 5.2143 14.0493 5.13859C13.921 5.06288 13.7721 5.0294 13.6238 5.04288C13.4294 5.05728 8.89554 5.44752 5.99034 7.78536C4.47474 9.18792 1.43994 17.3894 1.43994 24.48C1.43994 24.6067 1.47378 24.7277 1.5357 24.8371C3.62802 28.5163 9.3405 29.4775 10.6423 29.52H10.6646C10.7782 29.5203 10.8903 29.4937 10.9916 29.4424C11.093 29.3911 11.1808 29.3165 11.2478 29.2248L12.5632 27.4133C9.01146 26.4967 7.19706 24.9386 7.09338 24.8458C6.95017 24.7194 6.86303 24.5412 6.85115 24.3506C6.83927 24.1599 6.90361 23.9723 7.03002 23.8291C7.15643 23.6859 7.33456 23.5988 7.52522 23.5869C7.71588 23.575 7.90345 23.6394 8.04666 23.7658C8.08842 23.8054 11.4299 26.64 17.9999 26.64C24.5807 26.64 27.9223 23.7938 27.9561 23.7658C28.0998 23.6403 28.2874 23.5769 28.4777 23.5896C28.668 23.6023 28.8456 23.69 28.9713 23.8334C29.0335 23.9042 29.0812 23.9864 29.1117 24.0756C29.1421 24.1647 29.1546 24.259 29.1486 24.353C29.1426 24.447 29.1181 24.5389 29.0766 24.6235C29.035 24.708 28.9772 24.7836 28.9065 24.8458C28.8028 24.9386 26.9884 26.4967 23.4367 27.4133L24.7528 29.2248C24.8198 29.3164 24.9074 29.3909 25.0087 29.4422C25.1099 29.4935 25.2218 29.5202 25.3353 29.52H25.3569C26.6601 29.4775 32.3719 28.5156 34.4649 24.8371C34.5261 24.7277 34.5599 24.6067 34.5599 24.48C34.5599 17.3894 31.5251 9.18864 29.9699 7.7544V7.7544ZM13.3199 21.6C11.9275 21.6 10.7999 20.3112 10.7999 18.72C10.7999 17.1288 11.9275 15.84 13.3199 15.84C14.7124 15.84 15.8399 17.1288 15.8399 18.72C15.8399 20.3112 14.7124 21.6 13.3199 21.6ZM22.6799 21.6C21.2875 21.6 20.1599 20.3112 20.1599 18.72C20.1599 17.1288 21.2875 15.84 22.6799 15.84C24.0724 15.84 25.1999 17.1288 25.1999 18.72C25.1999 20.3112 24.0724 21.6 22.6799 21.6Z'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
/* Twitter icon */
|
||||
|
||||
.navbar-item-twitter:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.navbar-item-twitter:before {
|
||||
content: '';
|
||||
margin-top: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
html[data-theme='dark'] .navbar-item-twitter:before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
.navbar-item-email-signup {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
.navbar-item-github:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.navbar-item-github:before {
|
||||
content: '';
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
html[data-theme='dark'] .navbar-item-github:before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
62
web/src/pages/index.css
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
html {
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
/*
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
*/
|
||||
|
||||
/*
|
||||
font-family: Inter;
|
||||
*/
|
||||
|
||||
/* Fading out dots */
|
||||
/*
|
||||
background: linear-gradient(180deg,hsla(0,0%,100%,0) 0,#fff 300px),
|
||||
fixed 0 0 /20px 20px radial-gradient(#d1d1d1 1px,transparent 0),
|
||||
fixed 10px 10px /20px 20px radial-gradient(#d1d1d1 1px,transparent 0)
|
||||
*/
|
||||
|
||||
/** Honeycomb pattern
|
||||
*
|
||||
background:
|
||||
radial-gradient(circle farthest-side at 0% 50%,#fb1 23.5%,rgba(240,166,17,0) 0)21px 30px,
|
||||
radial-gradient(circle farthest-side at 0% 50%,#B71 24%,rgba(240,166,17,0) 0)19px 30px,
|
||||
linear-gradient(#fb1 14%,rgba(240,166,17,0) 0, rgba(240,166,17,0) 85%,#fb1 0)0 0,
|
||||
linear-gradient(150deg,#fb1 24%,#B71 0,#B71 26%,rgba(240,166,17,0) 0,rgba(240,166,17,0) 74%,#B71 0,#B71 76%,#fb1 0)0 0,
|
||||
linear-gradient(30deg,#fb1 24%,#B71 0,#B71 26%,rgba(240,166,17,0) 0,rgba(240,166,17,0) 74%,#B71 0,#B71 76%,#fb1 0)0 0,
|
||||
linear-gradient(90deg,#B71 2%,#fb1 0,#fb1 98%,#B71 0%)0 0 #fb1;
|
||||
background-size: 40px 60px;
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
div.twLandingPage {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
pre code {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,869 +1,60 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Layout from '@theme/Layout';
|
||||
import CodeBlockWithTitle from '../components/CodeBlockWithTitle'
|
||||
import EmailSignupForm from '../components/EmailSignupForm/index.js'
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Head from '@docusaurus/Head';
|
||||
import styles from './styles.module.css';
|
||||
import Modal from 'react-modal';
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: 'Quick start',
|
||||
//imageUrl: 'img/undraw_docusaurus_mountain.svg',
|
||||
description: (
|
||||
<>
|
||||
No more endless configuration files. Create a production-ready web app
|
||||
with just a few lines of code - we will set you up with all the best defaults.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Speed & Power',
|
||||
//imageUrl: 'img/undraw_docusaurus_tree.svg',
|
||||
description: (
|
||||
<>
|
||||
Move fast using Wasp's declarative language, but also
|
||||
drop down to <code>js/html/css...</code> when you require more control.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'No lock-in',
|
||||
//imageUrl: 'img/undraw_docusaurus_react.svg',
|
||||
description: (
|
||||
<>
|
||||
If Wasp becomes too limiting for you, simply eject and continue with the human-readable
|
||||
source code following industry best-practices.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
import Nav from '../components/Nav/index'
|
||||
import Hero from '../components/Hero'
|
||||
import Benefits from '../components/Benefits'
|
||||
import HowItWorks from '../components/HowItWorks'
|
||||
import ShowcaseGallery from '../components/ShowcaseGallery'
|
||||
import Newsletter from '../components/Newsletter'
|
||||
import Faq from '../components/Faq'
|
||||
import Footer from '../components/Footer'
|
||||
|
||||
function Feature({imageUrl, title, description}) {
|
||||
const imgUrl = useBaseUrl(imageUrl);
|
||||
import styles from './styles.module.css'
|
||||
import './index.css'
|
||||
import './preflight.css'
|
||||
|
||||
|
||||
const Background = () => {
|
||||
return (
|
||||
<div className={clsx('col col--4', styles.feature)}>
|
||||
{imgUrl && (
|
||||
<div className="text--center">
|
||||
<img className={styles.featureImage} src={imgUrl} alt={title} />
|
||||
</div>
|
||||
)}
|
||||
<h2>{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PageBreakWithLogo() {
|
||||
return (
|
||||
<section className={'section-lg'}>
|
||||
<div className="container"
|
||||
style={{ textAlign: 'center' }}>
|
||||
<img className="logo" src="img/eqpar-separator.png"/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function WaspLatestVersion() {
|
||||
const [latestRelease, setLatestRelease] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRelease = async () => {
|
||||
const response = await fetch(
|
||||
'https://api.github.com/repos/wasp-lang/wasp/releases'
|
||||
)
|
||||
console.log(response)
|
||||
const releases = await response.json()
|
||||
if (releases) {
|
||||
setLatestRelease(releases[0])
|
||||
}
|
||||
}
|
||||
fetchRelease()
|
||||
}, [])
|
||||
|
||||
if (latestRelease) {
|
||||
return (
|
||||
<h4 className={styles.waspVersion}>
|
||||
<a href={latestRelease.html_url}>
|
||||
{ latestRelease.name }
|
||||
</a>
|
||||
</h4>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function ProductHuntBadge() {
|
||||
return (
|
||||
<a href="https://www.producthunt.com/posts/wasp-lang-alpha?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-wasp-lang-alpha" target="_blank">
|
||||
<img
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=277135&theme=light&period=daily"
|
||||
alt="Wasp-lang Alpha - Develop web apps in React & Node.js with no boilerplate | Product Hunt"
|
||||
style={{width: '250px', height: '54px'}} width="250" height="54"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroCodeExample() {
|
||||
// NOTE: There is an image in static/img/hero-code-shot.png of this code,
|
||||
// used as the main image of the web app (specified via <meta>) when being
|
||||
// parsed by external sites (Facebook, Twitter, Reddit, ...).
|
||||
// Therefore, if this example changes, you should also update that image
|
||||
// (just take a screenshot).
|
||||
|
||||
const createAppWaspCode =
|
||||
`app todoApp {
|
||||
title: "ToDo App", /* visible in tab */
|
||||
|
||||
auth: { /* full-stack auth out-of-the-box */
|
||||
userEntity: User,
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
/* import your React code */
|
||||
component: import Main from "@client/Main.js"
|
||||
}
|
||||
`
|
||||
return (
|
||||
<CodeBlockWithTitle title="todoApp.wasp" language="css">
|
||||
{ createAppWaspCode }
|
||||
</CodeBlockWithTitle>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeExamples() {
|
||||
const CodeExample = Object.freeze({
|
||||
NEW_APP: 'Create a new app',
|
||||
DEFINE_ENTITY: 'Define and query a data model',
|
||||
ADD_AUTH: 'Add authentication'
|
||||
})
|
||||
|
||||
const getButtonTextForCodeExample = (codeExample) => CodeExample[codeExample]
|
||||
|
||||
const [currentCodeExample, setCodeExample] = useState(CodeExample.NEW_APP)
|
||||
|
||||
function CurrentCodeExample() {
|
||||
if (currentCodeExample === CodeExample.NEW_APP) {
|
||||
const createAppWaspCode =
|
||||
`/* global app settings */
|
||||
app todoApp {
|
||||
title: "ToDo App" /* browser tab title */
|
||||
}
|
||||
|
||||
/* routing */
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
component: import Main from "@client/Main" /* import your React code */
|
||||
}
|
||||
`
|
||||
|
||||
const createAppMainComponentCode =
|
||||
`import React from 'react'
|
||||
|
||||
export default () => <span> Hello World! </span>
|
||||
`
|
||||
return (
|
||||
<div className="codeExampleFiles">
|
||||
<CodeBlockWithTitle title="todoApp.wasp" language="css">
|
||||
{ createAppWaspCode }
|
||||
</CodeBlockWithTitle>
|
||||
|
||||
<CodeBlockWithTitle
|
||||
title="src/client/Main.js | External React code, imported above"
|
||||
language="jsx">
|
||||
{ createAppMainComponentCode }
|
||||
</CodeBlockWithTitle>
|
||||
|
||||
<div>
|
||||
That's it, this is the whole app! Run <code>wasp start</code> and check it out
|
||||
at <code>http://localhost:3000</code>!
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else if (currentCodeExample === CodeExample.ADD_AUTH) {
|
||||
const exampleCode =
|
||||
`app todoApp {
|
||||
/* ... */
|
||||
|
||||
/* full-stack auth out-of-the-box */
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
usernameAndPassword: {}
|
||||
}
|
||||
onAuthFailedRedirectTo: "/login"
|
||||
}
|
||||
}
|
||||
|
||||
/* ... */
|
||||
|
||||
/* username & password required because of the auth method above */
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
psl=}
|
||||
|
||||
page MainPage {
|
||||
authRequired: true, /* available only to logged in users */
|
||||
component: import Main from "@client/Main"
|
||||
}
|
||||
`
|
||||
|
||||
const mainUsingAuthCode =
|
||||
`import React from 'react'
|
||||
import Todo from './Todo.js'
|
||||
|
||||
/* Because of authRequired property in todoApp.wasp, this page is
|
||||
* available only to logged in users and prop 'user' is automatically provided by wasp. */
|
||||
export default ({ user }) => {
|
||||
return <Todo/>
|
||||
}
|
||||
`
|
||||
return (
|
||||
<div className="codeExampleFiles">
|
||||
<CodeBlockWithTitle title="todoApp.wasp" language="css">
|
||||
{ exampleCode }
|
||||
</CodeBlockWithTitle>
|
||||
<CodeBlockWithTitle title="src/client/Main.js | Checking if user is logged in" language="jsx">
|
||||
{ mainUsingAuthCode }
|
||||
</CodeBlockWithTitle>
|
||||
|
||||
<div>
|
||||
To learn more about authentication & authorization in Wasp, check
|
||||
the <Link to={useBaseUrl('/docs/language/features#authentication--authorization')}>docs</Link>.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else if (currentCodeExample === CodeExample.DEFINE_ENTITY) {
|
||||
const defineEntityWaspCode =
|
||||
`/* ... */
|
||||
|
||||
/* Data model is defined via Prisma Schema Language (PSL) */
|
||||
entity Task {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
description String
|
||||
isDone Boolean @default(false)
|
||||
psl=}
|
||||
|
||||
query getTasks {
|
||||
fn: import { getTasks } from "@server/queries.js", /* import Node.js function */
|
||||
/* A list of entities this query uses - useful for automatic invalidation and refetching */
|
||||
entities: [Task]
|
||||
}
|
||||
`
|
||||
const getTasksQueryCode =
|
||||
`export const getTasks = async (args, context) => {
|
||||
// Since we above declared this query is using Task, it is automatically injected in the
|
||||
// context.
|
||||
return context.entities.Task.findMany()
|
||||
}
|
||||
`
|
||||
const todoUsingGetTasksCode =
|
||||
`import React from 'react'
|
||||
import { useQuery } from '@wasp/queries'
|
||||
import getTasks from '@wasp/queries/getTasks.js'
|
||||
|
||||
export default () => {
|
||||
// Standard useQuery syntax, Wasp provides a thin wrapper around it.
|
||||
const { data: tasks } = useQuery(getTasks)
|
||||
return <Tasks tasks={tasks}/>
|
||||
}
|
||||
`
|
||||
return (
|
||||
<div className="codeExampleFiles">
|
||||
<CodeBlockWithTitle title="todoApp.wasp" language="css">
|
||||
{ defineEntityWaspCode }
|
||||
</CodeBlockWithTitle>
|
||||
<CodeBlockWithTitle
|
||||
title="src/server/queries.js | Node.js function imported in a query above"
|
||||
language="jsx"
|
||||
>
|
||||
{ getTasksQueryCode }
|
||||
</CodeBlockWithTitle>
|
||||
<CodeBlockWithTitle title="src/client/Todo.js | Invoking query from React code" language="jsx">
|
||||
{ todoUsingGetTasksCode }
|
||||
</CodeBlockWithTitle>
|
||||
|
||||
<div>
|
||||
To learn more about working with data in Wasp, check
|
||||
the <Link to={useBaseUrl('/docs/language/language/features#entity')}>docs</Link>.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
console.log('this should never happen.')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function Buttons() {
|
||||
|
||||
function Button({ codeExampleKey }) {
|
||||
return (
|
||||
<button
|
||||
className={clsx('button',
|
||||
'info',
|
||||
currentCodeExample === CodeExample[codeExampleKey] && 'is-active',
|
||||
'button--secondary'
|
||||
)}
|
||||
onClick={() => setCodeExample(CodeExample[codeExampleKey])}
|
||||
>
|
||||
{ getButtonTextForCodeExample(codeExampleKey) }
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
Object.keys(CodeExample).map((k, idx) => <Button codeExampleKey={k} key={idx} />)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row CodeExamples">
|
||||
<div className="ButtonTabs col col--3">
|
||||
<div>
|
||||
<Buttons/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col col--9">
|
||||
<CurrentCodeExample/>
|
||||
</div>
|
||||
<div className='absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none'>
|
||||
<span className={classNames(styles.leftLights, "opacity-100")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WaspGhStarsCount() {
|
||||
const LightsTwo = () => (
|
||||
<div className='absolute top-[1800px] lg:top-[1000px] left-0 w-full h-full overflow-hidden pointer-events-none'>
|
||||
<span className={classNames(styles.lightsTwo, "opacity-100")} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const Index = () => {
|
||||
return (
|
||||
<iframe
|
||||
src="https://ghbtns.com/github-btn.html?user=wasp-lang&repo=wasp&type=star&count=true&size=large"
|
||||
frameBorder="0"
|
||||
scrolling="0"
|
||||
width="160px" height="30px">
|
||||
</iframe>
|
||||
)
|
||||
}
|
||||
<div className='twLandingPage'>
|
||||
<Nav />
|
||||
<div className='min-h-screen'>
|
||||
<main>
|
||||
<Background />
|
||||
<div> {/* container */}
|
||||
|
||||
function WaspDiscordBadge() {
|
||||
return (
|
||||
<a href="https://discord.gg/rzdnErX">
|
||||
<img alt="discord" src="https://img.shields.io/discord/686873244791210014?label=chat%20@%20discord" height="29px" />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<Hero />
|
||||
<Benefits />
|
||||
|
||||
function EmailCta() {
|
||||
return (
|
||||
<section className={clsx('section-lg', 'emailCtaTop')} id="signup-atf">
|
||||
<div className="container">
|
||||
<LightsTwo />
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '1rem' }}>
|
||||
<div className="col col--8 col--offset-2">
|
||||
<EmailSignupForm placeholder="Enter your email to receive updates"/>
|
||||
</div>
|
||||
</div>
|
||||
<HowItWorks />
|
||||
<ShowcaseGallery />
|
||||
<Newsletter />
|
||||
<Faq />
|
||||
|
||||
</div> {/* eof container */}
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
function EmailAndGithubCta() {
|
||||
return (
|
||||
<section className={'section-lg bg-diff'} id="signup">
|
||||
<div className="container">
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>Stay up to date</h2>
|
||||
<h3>
|
||||
<p>
|
||||
Join our mailing list and be the first to know when we ship new features
|
||||
and updates!
|
||||
</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '1rem' }}>
|
||||
<div className="col col--8 col--offset-2">
|
||||
<EmailSignupForm/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered, 'section-text')}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h3>
|
||||
<p>
|
||||
Also, make sure to check
|
||||
out <Link to={'https://github.com/wasp-lang/wasp'}>Wasp repo
|
||||
on Github</Link> and express your support by
|
||||
giving us a star!
|
||||
</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '1rem' }}>
|
||||
<div className="col">
|
||||
<WaspGhStarsCount />
|
||||
<WaspDiscordBadge />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function SocialProofSection() {
|
||||
return (
|
||||
<section className={clsx('section-lg', 'bg-diff', styles.socialProofSection)}>
|
||||
<div className="container">
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
|
||||
<div className="col col--10 col--offset-1">
|
||||
<div className={clsx(styles.socialProof)}>
|
||||
<div className={clsx(styles.backedByYC)}>
|
||||
<img className={clsx(styles.ycLogo)} src="img/ycombinator-logo.png" />
|
||||
<span>backed by <strong>Y Combinator</strong></span>
|
||||
</div>
|
||||
<ProductHuntBadge />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> {/* End of row */}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function Home() {
|
||||
const context = useDocusaurusContext();
|
||||
const {siteConfig = {}} = context;
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = React.useState(false)
|
||||
|
||||
const todoTutorialUrl = useBaseUrl('docs/tutorials/todo-app');
|
||||
const waspRwaDemoUrl = 'https://wasp-rwa.netlify.app';
|
||||
|
||||
const modalStyles = {
|
||||
content : {
|
||||
top : '40%',
|
||||
left : '40%',
|
||||
transform : 'translate(-30%, -30%)',
|
||||
paddingTop : '4rem',
|
||||
background : 'var(--ifm-hero-background-color)',
|
||||
/*
|
||||
right : 'auto',
|
||||
bottom : 'auto',
|
||||
marginRight : '-50%',
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
function openModal() {
|
||||
setModalIsOpen(true);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setModalIsOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title}`}
|
||||
description={siteConfig.tagline}
|
||||
>
|
||||
<Head>
|
||||
<meta property="og:image" content={siteConfig.url + useBaseUrl('img/hero-code-shot.png')} />
|
||||
</Head>
|
||||
|
||||
<header className={clsx('hero', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
|
||||
<div className="row hero-row">
|
||||
<div className="col col--7">
|
||||
|
||||
<div className="hero-text-col">
|
||||
|
||||
<h2 className="hero-subtitle">{siteConfig.tagline}</h2>
|
||||
|
||||
<div className="hero-works-with">
|
||||
<h3 className="works-with-text">
|
||||
Describe high-level features with Wasp DSL and write the rest of your logic
|
||||
in React, Node.js and Prisma.
|
||||
</h3>
|
||||
<div className="hero-works-with-icons">
|
||||
<img src="img/react-logo.svg" />
|
||||
<img src="img/nodejs-logo.svg" />
|
||||
<img src="img/prisma-logo.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {/* End of col. */}
|
||||
|
||||
<div className="col col--5">
|
||||
<HeroCodeExample/>
|
||||
|
||||
</div> {/* End of col. */}
|
||||
|
||||
</div> {/* End of row. */}
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered, styles.tryWaspRow)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
|
||||
<div className={clsx(styles.tryWaspContainer)}>
|
||||
<div className={clsx(styles.startCliCmd)}>
|
||||
<span><code>curl -sSL https://get.wasp-lang.dev/installer.sh | sh</code></span>
|
||||
</div>
|
||||
|
||||
{/* TODO: Martin commented tihs out because both buttons started showing up on desktop after upgrading to newer docusaurus. This happens when built for deployment, not during serving for development. Instead, he replaced it with just one version of button, below, instead of having one button for mobile and one for desktop.
|
||||
<div className={clsx(styles.startButtonAndVersion, styles.visibleOnDesktopOnly)}>
|
||||
<button
|
||||
className={clsx(
|
||||
'button button--primary button--huge',
|
||||
styles.heroButton,
|
||||
)}
|
||||
onClick={openModal}
|
||||
>
|
||||
Try Wasp in 5 minutes →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={clsx(styles.startButtonAndVersion, styles.visibleOnMobileOnly)}>
|
||||
<Link
|
||||
className={clsx(
|
||||
'button button--primary button--huge',
|
||||
styles.heroButton,
|
||||
)}
|
||||
to={useBaseUrl('/docs')}
|
||||
>
|
||||
Try Wasp in 5 minutes →
|
||||
</Link>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div className={clsx(styles.startButtonAndVersion)}>
|
||||
<Link
|
||||
className={clsx(
|
||||
'button button--primary button--huge',
|
||||
styles.heroButton,
|
||||
)}
|
||||
to={useBaseUrl('/docs')}
|
||||
>
|
||||
Try Wasp in 5 minutes →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={clsx(styles.usingWindows)}>
|
||||
Using Windows? Check the instructions <Link to={useBaseUrl("/docs/#2-installation")}>here</Link>.
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={modalIsOpen}
|
||||
style={modalStyles}
|
||||
onRequestClose={closeModal}
|
||||
shouldCloseOnOverlayClick={true}
|
||||
>
|
||||
<div className="container">
|
||||
<div className={clsx('row')}>
|
||||
<div className="col col--10">
|
||||
|
||||
|
||||
<h2 className="modal-step-title">1. Open your terminal and run:</h2>
|
||||
<div className={clsx(styles.startCliCmd)} style={{ height: '40px' }}>
|
||||
<span><code>curl -sSL https://get.wasp-lang.dev/installer.sh | sh</code></span>
|
||||
</div>
|
||||
|
||||
<h2 className="modal-step-title "style={{marginTop: '4rem'}}>2. Create a new project:</h2>
|
||||
<div className={clsx(styles.startCliCmd)} style={{ height: '40px' }}>
|
||||
<span><code>wasp new MyFirstApp</code></span>
|
||||
</div>
|
||||
|
||||
<h2 className="modal-step-title" style={{marginTop: '4rem'}}>3. Run your first app:</h2>
|
||||
<div className={clsx(styles.startCliCmd)} style={{ height: '40px', marginRight: '10px' }}>
|
||||
<span><code>cd MyFirstApp && wasp start</code></span>
|
||||
</div>
|
||||
<span>
|
||||
That's it!
|
||||
Open <Link to='http://localhost:3000/'>http://localhost:3000</Link> and see how it looks like!
|
||||
</span>
|
||||
|
||||
<div style={{marginTop: '4rem'}}>
|
||||
<span>
|
||||
You ran into problems or want more details? Refer to the <Link to={useBaseUrl('/docs')}>docs</Link>.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</Modal>
|
||||
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '1rem' }}>
|
||||
<div className="col">
|
||||
<WaspGhStarsCount />
|
||||
<WaspDiscordBadge />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div> {/* End of row. */}
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
{/* Social proof */}
|
||||
<SocialProofSection />
|
||||
|
||||
{/* One-line explanation */}
|
||||
<section className={'section-lg'}>
|
||||
<div className="container">
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--12">
|
||||
<h3 className={'title'}>
|
||||
Wasp is an open source, declarative DSL for devs who want to <span className="title-strong">use modern web dev stack</span>
|
||||
|
||||
<span style={{ whiteSpace: 'nowrap' }}>
|
||||
(React <img src="img/react-logo.png" height="25px" />,
|
||||
Node.js <img src="img/node-logo.png" height="25px" />,
|
||||
Prisma <img src="img/prisma-logo.png" height="25px" />,
|
||||
...)
|
||||
</span>
|
||||
|
||||
<span className="title-strong">without writing boilerplate</span>.
|
||||
</h3>
|
||||
<h3>
|
||||
<p>Frontend, backend and deployment - all unified with one concise language.</p>
|
||||
<p>Zero configuration, all best practices.</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PageBreakWithLogo/>
|
||||
|
||||
{/* Wasp compilation */}
|
||||
<section className={'section-lg'} id="how-it-works">
|
||||
<div className="container">
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>How it works</h2>
|
||||
<h3>
|
||||
<p>
|
||||
Given <code>.wasp</code> + <code>.js</code>, <code>.css</code>, <code>...</code> files as an input, Wasp compiler behind the scene
|
||||
<span className="title-strong">generates the full
|
||||
source code of your web app</span> - front-end, back-end and deployment.
|
||||
</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '2rem' }}>
|
||||
<div className="col">
|
||||
<img
|
||||
className={'wasp-diagram'}
|
||||
src="img/wasp-compilation.png"
|
||||
alt="Wasp compilation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PageBreakWithLogo/>
|
||||
|
||||
{/* Features */}
|
||||
{features && features.length > 0 && (
|
||||
<section className={clsx(styles.features, 'bg-diff')}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{features.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<PageBreakWithLogo/>
|
||||
|
||||
{/* Quick to start, easy to scale */}
|
||||
<section className={'section-lg'} id="fast-and-scalable">
|
||||
<div className="container">
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>Quick to start, easy to scale</h2>
|
||||
<h3>
|
||||
<p>
|
||||
Wasp aims to be at least as flexible as the traditional web frameworks like Ruby on Rails.
|
||||
<br/>
|
||||
Start your project quickly with the best defaults and customize and scale it as it grows.
|
||||
</p>
|
||||
</h3>
|
||||
<h3>
|
||||
<p>As an example, we used Wasp to implement a copy of Medium:</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div> {/* End of row */}
|
||||
|
||||
<div className="row">
|
||||
<div className="col col--10 col--offset-1">
|
||||
<a href={waspRwaDemoUrl} target="_blank">
|
||||
<img
|
||||
className="rwa"
|
||||
src="img/rwa-screenshot.png"
|
||||
alt="RealWorldApp in Wasp"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h3>You can try out the deployed app <a href={waspRwaDemoUrl}>here</a> or check out the source code <a href="https://github.com/wasp-lang/wasp/tree/main/examples/realworld">here</a>.</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PageBreakWithLogo/>
|
||||
|
||||
{/* What can Wasp do */}
|
||||
<section className={'section-lg'} id="what-can-do">
|
||||
<div className="container">
|
||||
|
||||
<div className={clsx(styles.featuresAndRoadmap, 'row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>Features & Roadmap</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row')}>
|
||||
<div className="col col--6">
|
||||
<h3 className={styles.featureListTitle}>Alpha</h3>
|
||||
<ul className={clsx(styles.featuresList, styles.featuresListDone)}>
|
||||
<li> full-stack auth (username & password, Google) </li>
|
||||
<li> pages & routing </li>
|
||||
<li> blurs the line between client & server - define your server actions and queries and call them directly in your client code (RPC)! </li>
|
||||
<li> smart caching of server actions and queries (automatic cache invalidation) </li>
|
||||
<li> entity (data model) definition with Prisma.io </li>
|
||||
<li> ACL on frontend </li>
|
||||
<li> importing NPM dependencies </li>
|
||||
<li> background and scheduled jobs </li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col col--6">
|
||||
<h3 className={styles.featureListTitle}>Coming next</h3>
|
||||
<ul className={clsx(styles.featuresList, styles.featuresListComing)}>
|
||||
<li> ACL on backend </li>
|
||||
<li> one-click deployment </li>
|
||||
<li> more auth methods (Facebook, LinkedIn, ...) </li>
|
||||
<li> tighter integration of entities with other features </li>
|
||||
<li> themes and layouts </li>
|
||||
<li> support for explicitly defined server API </li>
|
||||
<li> inline JS - ability to mix JS code with Wasp code! </li>
|
||||
<li> Typescript support </li>
|
||||
<li> server-side rendering </li>
|
||||
<li> Visual Editor </li>
|
||||
<li> support for different languages on backend </li>
|
||||
<li> richer wasp language with better tooling </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PageBreakWithLogo/>
|
||||
|
||||
{/* The language */}
|
||||
<section className={'section-lg'} id="the-language">
|
||||
<div className="container">
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>The Language</h2>
|
||||
<h3>
|
||||
<p>
|
||||
Concepts such as <em>app</em>, <em>page</em>, <em>route</em>, <em>auth</em>
|
||||
etc. are baked into Wasp, providing the
|
||||
higher level of expressiveness.
|
||||
</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CodeExamples/>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Take the tutorial */}
|
||||
<section className={'section-lg'}>
|
||||
<div className="container">
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)}>
|
||||
<div className="col col--10 col--offset-1">
|
||||
<h2>Take the tutorial</h2>
|
||||
<h3>
|
||||
<p>
|
||||
Take the <Link to={todoTutorialUrl}> Todo App tutorial </Link> and build a full-fledged Todo app in Wasp!
|
||||
</p>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx('row', styles.responsiveCentered)} style={{ paddingTop: '2rem' }}>
|
||||
<div className="col">
|
||||
<img alt="How Todo App will work once it is done"
|
||||
src={useBaseUrl('img/todo-app-tutorial-intro.gif')}
|
||||
style={{ border: "1px solid black" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="join-the-list">
|
||||
<EmailAndGithubCta />
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
export default Index
|
||||
|
367
web/src/pages/preflight.css
Normal file
@ -0,0 +1,367 @@
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box; /* 1 */
|
||||
border-width: 0; /* 2 */
|
||||
border-style: solid; /* 2 */
|
||||
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--tw-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-moz-tab-size: 4; /* 3 */
|
||||
tab-size: 4; /* 3 */
|
||||
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
|
||||
font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0; /* 1 */
|
||||
line-height: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
.twLandingPage h1,
|
||||
.twLandingPage h2,
|
||||
.twLandingPage h3,
|
||||
.twLandingPage h4,
|
||||
.twLandingPage h5,
|
||||
.twLandingPage h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
.twLandingPage a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
font-weight: inherit; /* 1 */
|
||||
line-height: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
background-color: transparent; /* 2 */
|
||||
background-image: none; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
.twLandingPage blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
.twLandingPage figure,
|
||||
.twLandingPage p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
.twLandingPage ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1; /* 1 */
|
||||
color: theme('colors.gray.400', #9ca3af); /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
.twLandingPage img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
@ -1,221 +1,60 @@
|
||||
/* stylelint-disable docusaurus/copyright-header */
|
||||
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 4.5rem 0 0 0;
|
||||
text-align: left;
|
||||
.sectionSkewed {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
.startButtonAndVersion {
|
||||
display: inline-flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
align-items: flex-start;
|
||||
/*justify-content: flex-start;*/
|
||||
}
|
||||
|
||||
.startCliCmd {
|
||||
display: inline-flex;
|
||||
background: #2a2d3e;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
align-items: center;
|
||||
color: #bf9900;
|
||||
|
||||
height: 64px;
|
||||
|
||||
border-radius: var(--ifm-button-border-radius);
|
||||
}
|
||||
|
||||
.startCliCmd code {
|
||||
background: none;
|
||||
//color: #bfc7d5;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.startCliCmd > span::before {
|
||||
content: "$ ";
|
||||
}
|
||||
|
||||
.tryWaspRow {
|
||||
margin-bottom: 0rem; /* 6 */
|
||||
}
|
||||
|
||||
.tryWaspContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.usingWindows {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tryWaspContainer div:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.socialProofSection {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.socialProof {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.socialProof > *:not(:last-child) {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.ycLogo {
|
||||
max-width: 4rem;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
|
||||
.backedByYC {
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.waspVersion {
|
||||
align-self: center;
|
||||
text-decoration: underline;
|
||||
|
||||
}
|
||||
|
||||
.startButtonAndVersion > *:not(:first-child) {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: 100% 0;
|
||||
transform: skewY(-2deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.featureImage {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
.sectionSkewedContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.featureListTitle {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
.leftLights::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 1223px;
|
||||
height: 912px;
|
||||
|
||||
width: 1200px;
|
||||
height: 912px;
|
||||
|
||||
left: calc(50% - 1100px);
|
||||
top: -10%;
|
||||
|
||||
background: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
rgba(255, 214, 0, 0.2) 0%,
|
||||
rgba(255, 168, 0, 0) 100%
|
||||
);
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.featuresList {
|
||||
list-style-type: none;
|
||||
.lightsTwo::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 1223px;
|
||||
height: 912px;
|
||||
|
||||
width: 1200px;
|
||||
height: 912px;
|
||||
|
||||
left: calc(50%);
|
||||
top: 0px;
|
||||
|
||||
background: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
rgba(255, 214, 0, 0.2) 0%,
|
||||
rgba(255, 168, 0, 0) 100%
|
||||
);
|
||||
will-change: filter;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.featuresList li {
|
||||
padding-left: 1rem;
|
||||
text-indent: -0.2rem;
|
||||
}
|
||||
|
||||
.featuresList li:not(:last-child) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.featuresListComing li::before {
|
||||
margin-right: 0.5rem;
|
||||
content: "⏳ ";
|
||||
}
|
||||
|
||||
.featuresListDone li::before {
|
||||
margin-right: 0.5rem;
|
||||
content: "✅ ";
|
||||
}
|
||||
|
||||
.responsiveCentered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.featuresAndRoadmap {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.visibleOnMobileOnly {
|
||||
display: none;
|
||||
}
|
||||
.visibleOnDesktopOnly {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* NOTE(matija): it is important that this element is last! That way it overrides
|
||||
* the properties from above, otherwise it would get overriden since the properties above
|
||||
* are general. */
|
||||
|
||||
/* TODO(matija): there is probably a better way of doing this, I don't like being dependent on
|
||||
* the property order.*/
|
||||
@media screen and (max-width: 966px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.visibleOnMobileOnly {
|
||||
display: block;
|
||||
}
|
||||
.visibleOnDesktopOnly {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tryWaspContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tryWaspContainer > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.startButtonAndVersion {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.startButtonAndVersion > *:not(:first-child) {
|
||||
/* NOTE(matija): this is here to counter margin-left from above, the general case. Terrible, I know. */
|
||||
margin-left: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.socialProof {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.socialProof > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.heroButton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.startCliCmd code {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
}
|
||||
/* style={{ width: '300px', height: '60px', lineHeight: '40px', fontSize: '25px', margin: '40px 0px 0px 70px' }} */
|
||||
|
BIN
web/static/img/1000-gh-stars/1k_gh_stars_chart.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
web/static/img/1000-gh-stars/first-docs.png
Normal file
After Width: | Height: | Size: 145 KiB |