forked from Forge-Trade/forge-mastery
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
70 changed files
with
8,928 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"nuxt.isNuxtApp": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Forge Interactive Video Tutorials | ||
|
||
## Stack details | ||
|
||
- [TypeScript](https://www.typescriptlang.org/) | ||
- [Tailwind](https://tailwindcss.com/) for CSS styling | ||
- [Planetscale](https://planetscale.com/) for data persistence | ||
- [Prisma](https://www.prisma.io/) for ORM | ||
- [NextAuth](https://next-auth.js.org/) for authentication via GitHub | ||
- [Mux](https://mux.com) for video streaming and thumbnail generation | ||
- [Mux Player](https://docs.mux.com/guides/video/mux-player) for video playback | ||
- [Mux Uploader](https://github.com/muxinc/elements/tree/main/packages/mux-uploader-react) for video uploading | ||
|
||
## Progressive Decentralization | ||
|
||
I'm a cheapskate, everything will hopefully move to IPFS or Arweave. But ain't got time fo dat now. | ||
|
||
### Create a `.env.local` file | ||
|
||
This project uses several secrets to access the different accounts used throughout the codebase. You can configure these values locally by copying the `.env.local.example` file to a new file called `.env.local` and filling out the values as you receive them in the steps below. | ||
|
||
Also, don't forget to add the values to the project's environment variables in production on Vercel. | ||
|
||
### Mux account setup | ||
|
||
To authenticate this application with your Mux account, [create a new access token](https://dashboard.mux.com/settings/access-tokens) within the Mux dashboard and copy the access token ID and secret into your `.env.local` file and into your Vercel environment variables. | ||
|
||
### Database Setup | ||
|
||
First, make sure you have `mysql-client` installed locally so you can take full advantage of the `pscale` CLI tool down the road. On MacOS with Homebrew installed, you can run the following command in your terminal: | ||
|
||
``` | ||
brew install mysql-client | ||
``` | ||
|
||
Next, install the [Planetscale CLI](https://github.com/planetscale/cli). Again, on MacOS, this command will do the trick: | ||
|
||
``` | ||
brew install planetscale/tap/pscale | ||
``` | ||
|
||
Next, authorize the Planetscale CLI with your newly created account by running: | ||
|
||
``` | ||
pscale auth | ||
``` | ||
|
||
Create a new database in your Planetscale account called `video-course-starter-kit` | ||
``` | ||
pscale database create video-course-starter-kit | ||
``` | ||
Follow the link provided to you as a result of running this command to get the connection string for your database. | ||
|
||
![Click to get your connection string](public/images/pscale-connect.png) | ||
![Change your connection method to Prisma](public/images/pscale-prisma.png) | ||
![Copy the value and paste into your .env.local file](public/images/pscale-string.png) | ||
|
||
Copy the resulting authenticated database url value into your `.env.local` file and into your Vercel environment variables. | ||
|
||
We'll connect to this database locally by opening a connection to it on a local port. Here's how you can connect to the Planetscale database `video-course-starter-kit` on port 3309: | ||
|
||
``` | ||
pscale connect video-course-starter-kit main --port 3309 | ||
``` | ||
|
||
## Modifying the database schema | ||
|
||
If you'd like to make any changes to the database schema, you can do so by following these steps: | ||
|
||
``` | ||
pscale branch create video-course-starter-kit my-new-branch | ||
# after a few moments, close and reopen db proxy to the new branch | ||
pscale connect video-course-starter-kit my-new-branch --port 3309 | ||
# change your schema in the prisma/schema.prisma file... then, | ||
npx prisma generate | ||
npx prisma db push | ||
# when ready, make a deploy request | ||
pscale deploy-request create video-course-starter-kit my-new-branch | ||
# shipit | ||
pscale deploy-request deploy video-course-starter-kit 1 | ||
``` | ||
|
||
## Inspecting the database | ||
Prisma provides a nice interface to be able to load up your database contents and see the data that is powering your application. When you've connected to your Planetscale database, you can load up the Prisma GUI with the following command: | ||
|
||
``` | ||
npx prisma studio | ||
``` | ||
|
||
## Handling webhooks | ||
|
||
Mux uses webhooks to communicate the status of your uploaded video assets back to your application. To handle these webhooks locally, you'll first need to install [ngrok](https://ngrok.com/download). | ||
|
||
```shell | ||
brew install ngrok/ngrok/ngrok | ||
ngrok config add-authtoken <YOUR_NGROK_TOKEN> | ||
ngrok http 3000 | ||
``` | ||
|
||
Now, we need to make Mux aware of your ngrok URL. Visit [https://dashboard.mux.com/settings/webhooks](https://dashboard.mux.com/settings/webhooks) and add the tunnel URL listed in your terminal as a URL that Mux should notify with new events. | ||
|
||
> Make sure to append `/api/webhooks/mux` to the end of your tunnel URL. | ||
Then, copy the Webhook signing secret value and paste it into your `.env.local` file under `MUX_WEBHOOK_SECRET` | ||
|
||
### Run the development server | ||
|
||
Starting up the dev server is a simple one line command: | ||
|
||
``` | ||
yarn dev | ||
``` | ||
|
||
### GitHub OAuth setup | ||
|
||
End users of this video course application can authenticate with their GitHub account. As a prerequisite, you'll need to create an OAuth App on GitHub that associates a user's access to your application with your GitHub account. | ||
|
||
To create your OAuth app, follow these steps: | ||
|
||
1. Go to [https://github.com/settings/developers](https://github.com/settings/developers) | ||
|
||
2. Click "OAuth Apps" and create an Oauth application to use in Development: | ||
|
||
![Github Oauth Application Setup](./screenshots/github-oauth.png) | ||
|
||
| Application name | Homepage URL | Authorization callback URL | | ||
|--------------------------------|----------------------------------------------------|----------------------------| | ||
| Video Course Starter Kit (dev) | https://github.com/muxinc/video-course-starter-kit | http://localhost:3000/ | | ||
|
||
3. Copy the `GITHUB_ID` and `GITHUB_SECRET` and paste them into your environment variables on Vercel and in your `.env.local` file. | ||
|
||
> Note: when you deploy a production copy of this application, you'll need to create another GitHub OAuth app which uses your production URL as the "Authorization callback URL" value. | ||
## Recommended VS code extensions | ||
### Prisma | ||
The [Prisma extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) adds syntax highlighting, formatting, auto-completion, jump-to-definition and linting for .prisma files. | ||
|
||
## Questions? Comments? | ||
|
||
Tweet us [@MuxHQ](https://twitter.com/muxhq) or email [email protected] with anything you need help with. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
type Props = { | ||
children: React.ReactNode; | ||
}; | ||
|
||
const Banner = ({ children }: Props) => { | ||
return ( | ||
<div className='bg-slate-800 hover:bg-slate-900 text-white w-full p-4 block mb-8'> | ||
{children} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Banner; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import clsx from "clsx"; | ||
|
||
type Props = { | ||
children: React.ReactNode; | ||
onClick?: () => void; | ||
rest?: any; | ||
intent?: "primary" | "secondary" | "danger"; | ||
}; | ||
|
||
const Button = ({ children, intent = 'primary', ...rest }: Props) => { | ||
return ( | ||
<button | ||
className={clsx({ | ||
"px-4 py-3 rounded text-white my-4 inline-block w-fit": true, | ||
'bg-slate-700 hover:bg-slate-800': intent === 'primary', | ||
'text-slate-700 border border-slate-700': intent === 'secondary', | ||
"bg-red-600 hover:bg-red-700": intent === "danger", | ||
})} | ||
{...rest} | ||
> | ||
{children} | ||
</button> | ||
); | ||
}; | ||
|
||
export default Button; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Course, Lesson, Video } from "@prisma/client"; | ||
import Link from 'next/link' | ||
import Image from 'next/future/image' | ||
import Heading from './Heading' | ||
|
||
type Props = { | ||
isAdmin: boolean; | ||
course: Course & { | ||
lessons: (Lesson & { | ||
video: Video | null; | ||
})[]; | ||
} | ||
} | ||
|
||
const CourseCard = ({ course, isAdmin }: Props) => { | ||
const href = isAdmin ? `/admin/courses/${course.id}` : `/courses/${course.id}` | ||
return ( | ||
<> | ||
<Link href={href}> | ||
<a className='w-full border rounded-lg transition shadow-sm hover:shadow-md cursor-pointer'> | ||
{course.lessons[0]?.video?.publicPlaybackId && ( | ||
<Image | ||
className="w-full" | ||
src={`https://image.mux.com/${course.lessons[0]?.video?.publicPlaybackId}/thumbnail.jpg?width=640`} | ||
alt={`Video thumbnail preview for ${course.lessons[0]?.video?.publicPlaybackId}`} | ||
width={320} | ||
height={240} | ||
/> | ||
)} | ||
|
||
<div className="p-8"> | ||
{!course.published && (<span className="bg-slate-200 text-slate-700 rounded-full text-xs py-1 px-3 mb-2 inline-block">Draft</span>)} | ||
|
||
<Heading as="h3"> | ||
{course.name} | ||
</Heading> | ||
<p className="text-slate-700"> | ||
{course.description} | ||
</p> | ||
</div> | ||
</a> | ||
</Link> | ||
</> | ||
); | ||
}; | ||
|
||
export default CourseCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import CourseCard from 'components/CourseCard' | ||
import type { Course, Lesson, Video } from "@prisma/client" | ||
|
||
type Props = { | ||
isAdmin?: boolean; | ||
courses: (Course & { | ||
lessons: (Lesson & { | ||
video: Video | null; | ||
})[]; | ||
})[] | ||
} | ||
|
||
const CourseGrid = ({ courses, isAdmin = false }: Props) => { | ||
return ( | ||
<> | ||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'> | ||
{courses.map(course => ( | ||
<CourseCard key={course.id} course={course} isAdmin={isAdmin} /> | ||
))} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default CourseGrid; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import Image from 'next/future/image' | ||
import type { Course, Lesson, Video } from "@prisma/client" | ||
import Heading from 'components/Heading' | ||
import ReactMarkdown from 'react-markdown' | ||
|
||
type Props = { | ||
course: (Course & { | ||
lessons: (Lesson & { | ||
video: Video | null; | ||
})[]; | ||
}) | ||
} | ||
|
||
const CourseOverview = ({ course }: Props) => { | ||
return ( | ||
<> | ||
<div> | ||
<Heading>{course.name}</Heading> | ||
<p className='text-slate-700 mb-10'></p> | ||
|
||
<div className='prose lg:prose-xl'> | ||
<ReactMarkdown> | ||
{course.description} | ||
</ReactMarkdown> | ||
</div> | ||
|
||
<h2 className='text-slate-800 text-2xl mb-4 font-bold'>What you'll learn</h2> | ||
{course.lessons.map(lesson => ( | ||
<div key={lesson.id} className='flex flex-col md:flex-row gap-6 mb-8'> | ||
{lesson.video?.publicPlaybackId && ( | ||
<Image | ||
src={`https://image.mux.com/${lesson.video.publicPlaybackId}/thumbnail.jpg?width=640`} | ||
alt={`Video thumbnail preview for ${lesson.name}`} | ||
width={320} | ||
height={240} | ||
/> | ||
)} | ||
<div> | ||
<h2 className='text-xl font-semibold'>{lesson.name}</h2> | ||
<p>{lesson.description}</p> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default CourseOverview; | ||
|
||
|
Oops, something went wrong.