Skip to content
This repository was archived by the owner on Oct 21, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions apps/pay/app/[username]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"

import { ApolloQueryResult } from "@apollo/client"

import { getClient } from "../ssr-client"
import { apollo } from "../ssr-client"

import { defaultCurrencyMetadata } from "../currency-metadata"

Expand All @@ -24,10 +24,13 @@ type Props = {
export default async function UsernameLayout({ children, params }: Props) {
let response: ApolloQueryResult<AccountDefaultWalletsQuery> | { errorMessage: string }
try {
response = await getClient().query<AccountDefaultWalletsQuery>({
query: AccountDefaultWalletsDocument,
variables: { username: params.username },
})
response = await apollo
.unauthenticated()
.getClient()
.query<AccountDefaultWalletsQuery>({
query: AccountDefaultWalletsDocument,
variables: { username: params.username },
})
} catch (err) {
console.error("error in username-layout.tsx", err)
if (err instanceof Error) {
Expand Down
70 changes: 70 additions & 0 deletions apps/pay/app/api/auth/[...nextauth]/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextAuthOptions } from "next-auth"

import { env } from "@/env"
import { fetchUserData } from "@/app/graphql/quries/me-query"
import { MeQuery } from "@/lib/graphql/generated"

declare module "next-auth" {
interface Profile {
id: string
}
interface Session {
sub: string | null
accessToken: string
userData?: MeQuery
}
}

const type = "oauth" as const
export const authOptions: NextAuthOptions = {
providers: [
{
id: "blink",
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
wellKnown: `${env.HYDRA_PUBLIC}/.well-known/openid-configuration`,
authorization: {
params: { scope: "read" },
},
idToken: false,
name: "Blink",
type,
profile(profile) {
return {
id: profile.sub,
}
},
},
],
debug: process.env.NODE_ENV === "development",
secret: env.NEXTAUTH_SECRET,
callbacks: {
async jwt({ token, account, profile }) {
if (account) {
token.accessToken = account.access_token
token.expiresAt = account.expires_at
token.refreshToken = account.refresh_token
token.id = profile?.id
}
return token
},
async session({ session, token }) {
if (
!token.accessToken ||
!token.sub ||
typeof token.accessToken !== "string" ||
typeof token.sub !== "string"
) {
throw new Error("Invalid token")
}
const res = await fetchUserData({ token: token.accessToken })

if (!(res instanceof Error)) {
session.userData = res.data
}
session.sub = token.sub
session.accessToken = token.accessToken
return session
},
},
}
7 changes: 7 additions & 0 deletions apps/pay/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import NextAuth from "next-auth"

import { authOptions } from "./auth"

const handler = NextAuth(authOptions)

export { handler as GET, handler as POST }
32 changes: 32 additions & 0 deletions apps/pay/app/graphql/quries/me-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { gql } from "@apollo/client"

import { apollo } from "@/app/ssr-client"
import { MeDocument, MeQuery } from "@/lib/graphql/generated"

gql`
query me {
me {
id
username
}
}
`

export async function fetchUserData({ token }: { token: string }) {
const client = apollo.authenticated(token).getClient()

try {
const data = await client.query<MeQuery>({
query: MeDocument,
})
return data
} catch (err) {
if (err instanceof Error) {
console.error("error", err)
return new Error(err.message)
} else {
console.error("Unknown error")
return new Error("Unknown error")
}
}
}
15 changes: 13 additions & 2 deletions apps/pay/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ import "bootstrap/dist/css/bootstrap.css"
import Head from "next/head"
import Script from "next/script"

import { getServerSession } from "next-auth"

import { authOptions } from "./api/auth/[...nextauth]/auth"

import { ApolloWrapper } from "@/components/apollo-wrapper"
import { APP_DESCRIPTION } from "@/config/config"

import SessionProvider from "@/components/session-provider"

const inter = Inter_Tight({ subsets: ["latin"] })

export const metadata: Metadata = {
title: "Blink Cash Register",
description: "Blink official lightning network node",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession(authOptions)
const token = session?.accessToken

return (
<html lang="en">
<Head>
Expand All @@ -44,7 +53,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
`}
</Script>
<body className={inter.className}>
<ApolloWrapper>{children}</ApolloWrapper>
<SessionProvider>
<ApolloWrapper authToken={token}>{children}</ApolloWrapper>
</SessionProvider>
</body>
</html>
)
Expand Down
17 changes: 15 additions & 2 deletions apps/pay/app/ssr-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,25 @@ import {

import { env } from "@/env"

export const { getClient } = registerApolloClient(() => {
type ClientOptions = {
token?: string
}

const createApolloClient = (options?: ClientOptions) => {
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link: new HttpLink({
uri: env.CORE_GQL_URL_INTRANET,
fetchOptions: { cache: "no-store" },
headers: {
...(options?.token ? { ["Oauth2-Token"]: options.token } : {}),
},
}),
})
})
}

export const apollo = {
authenticated: (token: string) =>
registerApolloClient(() => createApolloClient({ token })),
unauthenticated: () => registerApolloClient(() => createApolloClient()),
}
33 changes: 29 additions & 4 deletions apps/pay/components/apollo-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,25 @@ import { getMainDefinition } from "@apollo/client/utilities"
import { GraphQLWsLink } from "@apollo/client/link/subscriptions"
import { createClient } from "graphql-ws"

import { setContext } from "@apollo/client/link/context"

import { getClientSideGqlConfig } from "@/config/config"

function makeClient() {
function makeClient({ authToken }: { authToken: string | undefined }) {
const httpLink = new HttpLink({
uri: getClientSideGqlConfig().coreGqlUrl,
fetchOptions: { cache: "no-store" },
})

const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
...(authToken ? { ["Oauth2-Token"]: authToken } : {}),
},
}
})

const wsLink = new GraphQLWsLink(
createClient({
url: getClientSideGqlConfig().coreGqlWebSocketUrl,
Expand Down Expand Up @@ -69,6 +80,11 @@ function makeClient() {
},
})

let arrayLink = [errorLink, retryLink, httpLink]
if (authToken) {
arrayLink = [authLink, ...arrayLink]
}

const link = split(
({ query }) => {
const definition = getMainDefinition(query)
Expand All @@ -78,7 +94,7 @@ function makeClient() {
)
},
wsLink,
ApolloLink.from([errorLink, retryLink, httpLink]),
ApolloLink.from(arrayLink),
)

return new NextSSRApolloClient({
Expand All @@ -95,6 +111,15 @@ function makeClient() {
})
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
return <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>
type ApolloWrapperProps = {
children: React.ReactNode
authToken?: string
}

export function ApolloWrapper({ children, authToken }: ApolloWrapperProps) {
const client = makeClient({ authToken })

return (
<ApolloNextAppProvider makeClient={() => client}>{children}</ApolloNextAppProvider>
)
}
3 changes: 3 additions & 0 deletions apps/pay/components/session-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use client"
import { SessionProvider } from "next-auth/react"
export default SessionProvider
44 changes: 40 additions & 4 deletions apps/pay/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { OverlayTrigger, Tooltip } from "react-bootstrap"

import { useState } from "react"

import { signIn, signOut, useSession } from "next-auth/react"

import CurrencyDropdown from "../currency/currency-dropdown"
import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTrigger } from "../sheet"
import PinToHomescreen from "../pin-to-homescreen"
Expand All @@ -32,6 +34,9 @@ function updateCurrencyAndReload(newDisplayCurrency: string): void {
export function SideBar({ username }: { username: string }) {
const router = useRouter()
const pathName = usePathname()
const session = useSession()
const signedInUser = session?.data?.userData?.me

const [copied, setCopied] = useState(false)
const [memoChecked, setMemoChecked] = useState(
typeof window !== "undefined"
Expand Down Expand Up @@ -96,11 +101,42 @@ export function SideBar({ username }: { username: string }) {
<span className="block w-8 h-0.5 bg-black"></span>
</button>
</SheetTrigger>
<SheetContent onOpenAutoFocus={(e) => e.preventDefault()}>
<SheetContent
className="overflow-y-auto"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<SheetHeader>
<p className="text-xl font-semibold text-left">Pay {username}</p>
<p className="text-xl font-semibold text-left m-0">Pay {username}</p>
</SheetHeader>
<div className="grid gap-3 py-3 ">
<div
className="flex flex-col gap-0 bg-slate-200 p-2 m-0 rounded-md"
onClick={() => {
if (!signedInUser) signIn("blink")
}}
>
{signedInUser ? (
<>
<div className="flex justify-between">
<p className="text-md font-semibold mb-1">Signed in as</p>
<Image
className="cursor-pointer"
onClick={() => signOut()}
alt="logout"
src={"/icons/logout.svg"}
width={20}
height={20}
></Image>
</div>
<p className="text-sm mb-0">
{signedInUser.username || signedInUser.id}
</p>
</>
) : (
<p className="text-md font-semibold mb-1">Sign in</p>
)}
</div>

{Links.map((link) =>
pathName === link.href ? (
<span
Expand Down Expand Up @@ -169,8 +205,8 @@ export function SideBar({ username }: { username: string }) {
</button>
</OverlayTrigger>
</div>
<div className="flex flex-row justify-between align-middle align-content-center bg-slate-100 p-2 m-0 rounded-md">
<p className="m-0 ">Show Memo</p>
<div className="flex flex-row justify-between align-middle align-content-center m-0 rounded-md">
<p className="mb-4 font-semibold">Memo</p>
<Switch checked={memoChecked} onCheckedChange={handleMemoShow} />
</div>
<div className="flex flex-col items-center justify-center gap-3 mt-2">
Expand Down
11 changes: 11 additions & 0 deletions apps/pay/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export const env = createEnv({
REDIS_0_DNS: z.string().optional(),
REDIS_1_DNS: z.string().optional(),
REDIS_2_DNS: z.string().optional(),
// hydra
CLIENT_ID: z.string().default("CLIENT_ID"),
CLIENT_SECRET: z.string().default("CLIENT_SECRET"),
HYDRA_PUBLIC: z.string().default("http://localhost:4444"),
NEXTAUTH_URL: z.string().default(""),
NEXTAUTH_SECRET: z.string().default("secret"),
},
// DO NOT USE THESE, EXCEPT FOR LOCAL DEVELOPMENT
client: {
Expand All @@ -28,5 +34,10 @@ export const env = createEnv({
REDIS_0_DNS: process.env.REDIS_0_DNS, // Optional but required for Nostr Zaps
REDIS_1_DNS: process.env.REDIS_1_DNS, // Optional but required for Nostr Zaps
REDIS_2_DNS: process.env.REDIS_2_DNS, // Optional but required for Nostr Zaps
CLIENT_ID: process.env.CLIENT_ID,
CLIENT_SECRET: process.env.CLIENT_SECRET,
HYDRA_PUBLIC: process.env.HYDRA_PUBLIC,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
},
})
Loading