Welcome to Patch (pat or scratch), an interactive web application that is geared towards pet lovers. Initially, it will start out an application, but it's ready to become the social media platform for pets...
This repository uses TailwindCSS for styling. For the best developer experience, install the TailwindCSS IntelliSense extension for VSCode.
- Express
- Knex
- PostgreSQL # for deployment
- SQLite3 # for development
To run this project:
# clone to your local machine
cd patch
npm install
npm run db:reset # will run migrations and seeds
npm run dev
# you can find the server running on http://localhost:3000
To preview what a production build would look like:
npm run preview # this builds the client and serves it
# statically from the Express server
NOTE: Only do this when you want to view the staging build, use npm run dev
for development.
To share Hot Module Replacement (HMR) with your pair over liveshare, ensure that you are sharing both port 3000 and 3001. This is because vite's websocket server runs on port 3001.
Wireframes live on a Miro Board, here's a quick snapshot of what they look like, but check in Discord for the link!
Response:
pet: {
id: 1,
name: "Bella",
age: 2,
animal: "dog",
bio: "Bella is a sweet dog who loves to play fetch",
image_url: "https://images.unsplash.com/photo-1608744882201-52a7f7f3dd60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80",
}
Note: Requires authentication
Response:
pets: [
{
id: 1,
name: 'Bella',
age: 2,
animal: 'dog',
bio: 'Bella is a sweet dog who loves to play fetch',
image_url:
'https://images.unsplash.com/photo-1608744882201-52a7f7f3dd60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80',
scratchPoints: 11987,
patPoints: 7632,
impressions: 21983,
created_at: '2021-01-01T00:00:00.000Z',
updated_at: '2021-01-01T00:00:00.000Z',
},
{
id: 1,
name: 'Charlotte',
age: 4,
animal: 'cat',
bio: 'A creature of darkness, she will eat your food, and then you',
image_url:
'https://images.unsplash.com/photo-1592194996308-7b43878e84a6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80',
scratchPoints: 32321,
patPoints: 5,
impressions: 32326,
created_at: '2021-01-01T00:00:00.000Z',
updated_at: '2021-01-01T00:00:00.000Z',
},
]
Note: Requires authentication
Request:
{
name: "Bella",
age: 2,
animal: "dog",
bio: "Bella is a sweet dog who loves to play fetch",
image_url: "https://images.unsplash.com/photo-1608744882201-52a7f7f3dd60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80",
}
Note: Requires authentication
Request:
{
petId: 1,
authorId: 'auth0|123',
content: "I think Charlotte is adorable... Am I free to go?",
}
Response: 201 Created
Note: Requires authentication
Request:
{
petId: 1,
authorId: 'auth0|123',
vote: "pat", // | "scratch" | "skip"
}
Response: 200 OK
// actions/fruits.js
const FETCH_FRUITS_REQUEST = 'FETCH_FRUITS_REQUEST'
const FETCH_FRUITS_SUCCESS = 'FETCH_FRUITS_SUCCESS'
const FETCH_FRUITS_FAILURE = 'FETCH_FRUITS_FAILURE'
const fetchFruitsRequest = () => ({
type: FETCH_FRUITS_REQUEST,
})
const fetchFruitsSuccess = (fruits) => ({
type: FETCH_FRUITS_SUCCESS,
payload: fruits,
})
const fetchFruitsFailure = (error) => ({
type: FETCH_FRUITS_FAILURE,
payload: error,
})
const fetchFruits = () => (dispatch) => {
dispatch({ type: 'FETCH_FRUITS_REQUEST' })
dispatch(fetchFruitsRequest())
getFruits()
.then((fruits) => {
dispatch(fetchFruitsSuccess(fruits))
})
.catch((error) => {
dispatch(fetchFruitsFailure(error))
})
}
// Component.jsx
useEffect(() => {
dispatch(fetchFruits())
}, [])
// Component.jsx
const [fruits, setFruits] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
function fetchFruits() {
setLoading(true)
getFruits()
.then((fruits) => {
setFruits(fruits)
})
.catch((err) => {
setError(err)
})
.finally(() => {
setLoading(false)
})
}
useEffect(() => {
getFruits()
})
// Component.jsx
const [fruits, setFruits] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const { getAccessTokenSilently } = useAuth0()
async function fetchForbiddenFruits() {
try {
const token = await getAccessTokenSilently() // requires user to be authenticated
setLoading(true)
const fruits = await getForbiddenFruits(token)
setFruits(fruits)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchForbiddenFruits()
}, [])
// api/fruits.js
function getForbiddenFruits(token) {
return request
.get('/api/v1/fruits')
.set('Authorization', `Bearer ${token}`)
.then((res) => res.body)
}
// server/routes/fruits.router.js
router.get('/', checkJwt, (req, res) => {
// req.auth is available here
const userId = req.auth.sub
db.getForbiddenFruits(userId)
// .then(...)
// .catch(...)
})