Skip to content

Commit

Permalink
Convert with-redis example to TypeScript (#34720)
Browse files Browse the repository at this point in the history
With the recent push to get the examples in TypeScript, I thought I'd convert this `with-redis` example over.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [x] Make sure the linting passes by running `yarn lint`
  • Loading branch information
brodyd795 committed Feb 23, 2022
1 parent 68e48a0 commit 33d9615
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 22 deletions.
File renamed without changes.
5 changes: 5 additions & 0 deletions examples/with-redis/next-env.d.ts
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
7 changes: 6 additions & 1 deletion examples/with-redis/package.json
Expand Up @@ -19,6 +19,11 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@tailwindcss/jit": "0.1.1"
"@tailwindcss/jit": "0.1.1",
"@types/ioredis": "4.28.8",
"@types/node": "^16.11.25",
"@types/react": "^17.0.2",
"@types/uuid": "8.3.4",
"typescript": "^4.5.5"
}
}
@@ -1,7 +1,9 @@
import type { AppProps } from 'next/app'

import '../styles/globals.css'
import { Toaster } from 'react-hot-toast'

function MyApp({ Component, pageProps }) {
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
Expand Down
@@ -1,8 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { v4 as uuidv4 } from 'uuid'

import redis from '../../lib/redis'

export default async function create(req, res) {
export default async function create(
req: NextApiRequest,
res: NextApiResponse
) {
const { title } = req.body

if (!title) {
Expand Down
@@ -1,6 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next'

import redis from '../../lib/redis'

export default async function getAllFeatures(req, res) {
export default async function getAllFeatures(
req: NextApiRequest,
res: NextApiResponse
) {
const features = (await redis.hvals('features'))
.map((entry) => JSON.parse(entry))
.sort((a, b) => b.score - a.score)
Expand Down
@@ -1,6 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next'

import redis from '../../lib/redis'

export default async function subscribe(req, res) {
export default async function subscribe(
req: NextApiRequest,
res: NextApiResponse
) {
const { email } = req.body

if (email && validateEmail(email)) {
Expand All @@ -15,7 +20,7 @@ export default async function subscribe(req, res) {
}
}

function validateEmail(email) {
function validateEmail(email: string) {
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(String(email).toLowerCase())
Expand Down
@@ -1,6 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next'

import redis from '../../lib/redis'

export default async function upvote(req, res) {
export default async function upvote(
req: NextApiRequest,
res: NextApiResponse
) {
const { title, id } = req.body
const ip =
req.headers['x-forwarded-for'] || req.headers['Remote_Addr'] || 'NA'
Expand Down
@@ -1,13 +1,22 @@
import { useState, useRef } from 'react'
import type { NextApiRequest } from 'next'
import type { MouseEvent } from 'react'
import Head from 'next/head'
import clsx from 'clsx'
import useSWR, { mutate } from 'swr'
import toast from 'react-hot-toast'
import redis from '../lib/redis'

const fetcher = (url) => fetch(url).then((res) => res.json())
type Feature = {
id: string
title: string
score: number
ip: string
}

const fetcher = (url: string) => fetch(url).then((res) => res.json())

function LoadingSpinner({ invert }) {
function LoadingSpinner({ invert }: { invert?: boolean }) {
return (
<svg
className={clsx(
Expand Down Expand Up @@ -35,8 +44,20 @@ function LoadingSpinner({ invert }) {
)
}

function Item({ isFirst, isLast, isReleased, hasVoted, feature }) {
const upvote = async (e) => {
function Item({
isFirst,
isLast,
isReleased,
hasVoted,
feature,
}: {
isFirst: boolean
isLast: boolean
isReleased: boolean
hasVoted: boolean
feature: Feature
}) {
const upvote = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault()

const res = await fetch('/api/vote', {
Expand Down Expand Up @@ -85,11 +106,17 @@ function Item({ isFirst, isLast, isReleased, hasVoted, feature }) {
)
}

export default function Roadmap({ features, ip }) {
export default function Roadmap({
features,
ip,
}: {
features: Feature[]
ip: string
}) {
const [isCreateLoading, setCreateLoading] = useState(false)
const [isEmailLoading, setEmailLoading] = useState(false)
const featureInputRef = useRef(null)
const subscribeInputRef = useRef(null)
const featureInputRef = useRef<HTMLInputElement>(null)
const subscribeInputRef = useRef<HTMLInputElement>(null)

const { data, error } = useSWR('/api/features', fetcher, {
initialData: { features },
Expand All @@ -99,13 +126,13 @@ export default function Roadmap({ features, ip }) {
toast.error(error)
}

const addFeature = async (e) => {
const addFeature = async (e: MouseEvent<HTMLFormElement>) => {
e.preventDefault()
setCreateLoading(true)

const res = await fetch('/api/create', {
body: JSON.stringify({
title: featureInputRef.current.value,
title: featureInputRef?.current?.value ?? '',
}),
headers: {
'Content-Type': 'application/json',
Expand All @@ -122,16 +149,18 @@ export default function Roadmap({ features, ip }) {
}

mutate('/api/features')
featureInputRef.current.value = ''
if (featureInputRef.current) {
featureInputRef.current.value = ''
}
}

const subscribe = async (e) => {
const subscribe = async (e: MouseEvent<HTMLFormElement>) => {
e.preventDefault()
setEmailLoading(true)

const res = await fetch('/api/subscribe', {
body: JSON.stringify({
email: subscribeInputRef.current.value,
email: subscribeInputRef?.current?.value ?? '',
}),
headers: {
'Content-Type': 'application/json',
Expand All @@ -147,7 +176,10 @@ export default function Roadmap({ features, ip }) {
}

toast.success('You are now subscribed to feature updates!')
subscribeInputRef.current.value = ''

if (subscribeInputRef.current) {
subscribeInputRef.current.value = ''
}
}

return (
Expand Down Expand Up @@ -189,7 +221,7 @@ export default function Roadmap({ features, ip }) {
</form>
</div>
<div className="w-full">
{data.features.map((feature, index) => (
{data.features.map((feature: Feature, index: number) => (
<Item
key={index}
isFirst={index === 0}
Expand Down Expand Up @@ -254,7 +286,7 @@ export default function Roadmap({ features, ip }) {
)
}

export async function getServerSideProps({ req }) {
export async function getServerSideProps({ req }: { req: NextApiRequest }) {
const ip =
req.headers['x-forwarded-for'] || req.headers['Remote_Addr'] || 'NA'
const features = (await redis.hvals('features'))
Expand Down
21 changes: 21 additions & 0 deletions examples/with-redis/tsconfig.json
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": ".",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

0 comments on commit 33d9615

Please sign in to comment.