Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert with-redis example to TypeScript #34720

Merged
merged 9 commits into from Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"]
}