Skip to content

Commit

Permalink
Merge pull request #13 from Blazity/stock-remote-source
Browse files Browse the repository at this point in the history
Stock remote source
  • Loading branch information
Pierniki committed Sep 14, 2023
2 parents a38dd51 + d7b5621 commit 5ea8274
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export default function Layout({ children, params }: { children: React.ReactNode
<GoogleAnalytics />
<Providers>
<body>
<main className="main mx-auto flex max-w-[1200px] flex-col items-center justify-start">
<nav className="flex w-full justify-end gap-4 px-4 py-8">
<main className="mx-auto flex max-w-[1200px] flex-col items-center justify-start py-8">
<nav className="flex w-full justify-end gap-4 px-4">
<DynamicSearchDialog lang={lang} />
<DynamicLangSelect lang={lang} />
</nav>
Expand Down
4 changes: 4 additions & 0 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StockDisplay } from "components/StockDisplay/StockDisplay"
import { Locale } from "i18n.js"
import { RecentArticles } from "../../components/RecentArticles/RecentArticles"

Expand All @@ -21,6 +22,9 @@ export const metadata = {
export default async function Web({ params: { lang } }: { params: { lang: Locale } }) {
return (
<>
<div className="flex w-full justify-end px-4 pt-4">
<StockDisplay />
</div>
<RecentArticles lang={lang} />
</>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/RecentArticles/RecentArticles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HygraphClient } from "hygraphClient"
import { Locale } from "i18n"
import { RecentArticlesInfinite } from "./RecentArticlesInfinite"
import { RecentArticlesInfiniteDynamic } from "./RecentArticlesInfiniteDynamic"

export const RECENT_ARTICLES_PER_PAGE = 4

Expand All @@ -11,7 +11,7 @@ export async function RecentArticles({ lang }: { lang: Locale }) {
return (
<section className="w-full px-4">
<h2 className="mb-4 text-2xl font-bold">Recent articles</h2>
<RecentArticlesInfinite initialArticles={initialArticles} lang={lang} />
<RecentArticlesInfiniteDynamic initialArticles={initialArticles} lang={lang} />
</section>
)
}
7 changes: 4 additions & 3 deletions src/components/RecentArticles/RecentArticlesInfinite.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"use client"

import { InfiniteData, useInfiniteQuery, useQuery } from "@tanstack/react-query"
import { useInfiniteQuery } from "@tanstack/react-query"
import Image from "next/image"
import Link from "next/link"
import { useState } from "react"
import { Button } from "components/ui/Button/Button"
import { GetRecentArticlesQuery } from "gql/graphql"
import { HygraphClient } from "hygraphClient"
import { Locale } from "i18n"
import { RECENT_ARTICLES_PER_PAGE } from "./RecentArticles"

type RecentArticlesInfiniteProps = {
export type RecentArticlesInfiniteProps = {
initialArticles: GetRecentArticlesQuery
lang: Locale
}
Expand Down Expand Up @@ -71,3 +70,5 @@ export function RecentArticlesInfinite({ initialArticles, lang }: RecentArticles
</>
)
}

export default RecentArticlesInfinite
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use client"

import dynamic from "next/dynamic"
import type { RecentArticlesInfiniteProps } from "./RecentArticlesInfinite"

export const RecentArticlesInfiniteDynamic = dynamic<RecentArticlesInfiniteProps>(() =>
import("./RecentArticlesInfinite").then((mod) => mod.default)
)
51 changes: 51 additions & 0 deletions src/components/StockDisplay/StockDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useMemo } from "react"
import { z } from "zod"
import { HygraphClient } from "hygraphClient"
import { StockDisplayRenderer } from "./StockDisplayRenderer"

type StockQuoteBase = {
id: string
name: string
}
type ValidStockQuote = StockQuoteBase & { quote: AlphaVantageQuote }

type AlphaVantageQuote = {
"Global Quote": {
"10. change percent": number
}
}

const SYMBOLS_TO_FETCH = ["SPY", "AAPL", "AMZN", "GOOGL", "MSFT"]

export async function StockDisplay() {
const { getStockDailyQuotes } = HygraphClient()
const { stockDailyQuotes: quotes } = await getStockDailyQuotes(
{ symbols: SYMBOLS_TO_FETCH },
{ next: { revalidate: 60 * 60 } }
)

const validStockQuotes = useMemo(
() =>
quotes
.map((stockQuote) => ({ ...stockQuote, quote: validateQuote(stockQuote.quote) }))
.filter((stockQuote): stockQuote is ValidStockQuote => stockQuote.quote !== null)
.map(({ quote, ...stockQuoteProps }) => ({
id: stockQuoteProps.id,
name: stockQuoteProps.name,
changePercent: quote["Global Quote"]["10. change percent"],
})),
[quotes]
)

return <StockDisplayRenderer quotes={validStockQuotes} />
}

function validateQuote(stockQuote: unknown) {
const result = quoteSchema.safeParse(stockQuote)
if (!result.success) return null
return result.data
}

const quoteSchema = z.object({
"Global Quote": z.object({ "10. change percent": z.string().transform((val) => parseFloat(val.slice(0, -1))) }),
})
53 changes: 53 additions & 0 deletions src/components/StockDisplay/StockDisplayRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client"

import { MoveDown, MoveUp } from "lucide-react"
import { useEffect, useState } from "react"
import { cn } from "utils/cn"

type StockDisplayRendererProps = {
quotes: { name: string; id: string; changePercent: number }[]
}

export function StockDisplayRenderer({ quotes }: StockDisplayRendererProps) {
const [currentIndex, setCurrentIndex] = useState(0)
const [visible, setVisible] = useState<boolean>(true)

useEffect(() => {
const interval = setInterval(() => {
setVisible(false)
}, 4000)

return () => {
clearInterval(interval)
}
}, [])

useEffect(() => {
if (visible) return
const timeoutId = setTimeout(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % quotes.length)
setVisible(true)
}, 1000)

return () => {
clearTimeout(timeoutId)
}
}, [visible, quotes.length])

const currentQuote = quotes[currentIndex]

if (!currentQuote) return

const isNegative = currentQuote.changePercent < 0

return (
<div className={cn("flex gap-4 text-sm transition-opacity duration-1000", visible ? "opacity-100" : "opacity-0")}>
{currentQuote.name}
<span className={cn("flex items-center gap-1", isNegative ? "text-red-800" : "text-green-800")}>
{!isNegative && "+"}
{currentQuote.changePercent.toFixed(2)}%
{isNegative ? <MoveDown className="mt-0.5 h-3 w-3" /> : <MoveUp className="mt-0.5 h-3 w-3" />}
</span>
</div>
)
}
29 changes: 27 additions & 2 deletions src/hygraphClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TypedDocumentNode } from "@graphql-typed-document-node/core"
import { GraphQLClient, Variables } from "graphql-request"
import type { HygraphLocaleEnum, Locale } from "i18n.js"
import type { HygraphLocaleEnum, Locale } from "i18n"
import { i18n } from "i18n"
import { env } from "./env.mjs"
import { graphql } from "./gql"

Expand Down Expand Up @@ -62,7 +63,29 @@ const getRecentArticles = graphql(`
}
`)

export const HygraphClient = (inputLocale: Locale) => {
const getHomepage = graphql(`
query getHomepage($locales: [Locale!]!) {
homepages(locales: $locales) {
stockDailyQuotes {
id
name
quote
}
}
}
`)

const getStockDailyQuotes = graphql(`
query getStockDailyQuotes($symbols: [String!]!) {
stockDailyQuotes(where: { symbol_in: $symbols }) {
id
name
quote
}
}
`)

export const HygraphClient = (inputLocale: Locale = i18n.defaultLocale) => {
const locale = inputLocale.replace("-", "_") as HygraphLocaleEnum

const makeRequest =
Expand All @@ -76,5 +99,7 @@ export const HygraphClient = (inputLocale: Locale) => {
getArticles: makeRequest(getArticles),
getArticleSummary: makeRequest(getArticleSummary),
getRecentArticles: makeRequest(getRecentArticles),
getHomepage: makeRequest(getHomepage),
getStockDailyQuotes: makeRequest(getStockDailyQuotes),
}
}

0 comments on commit 5ea8274

Please sign in to comment.