From 2ae8dbeba561f60e0d9510bb477252f37a77b3df Mon Sep 17 00:00:00 2001 From: swve Date: Tue, 4 Jun 2024 23:12:01 +0100 Subject: [PATCH] feat: major login and signup changes --- apps/web/app/auth/cookies.ts | 70 ------------------ .../[orgslug] => auth}/forgot/forgot.tsx | 0 .../{orgs/[orgslug] => auth}/forgot/page.tsx | 0 apps/web/app/auth/layout.tsx | 19 +++++ apps/web/app/{ => auth}/login/login.tsx | 0 apps/web/app/{ => auth}/login/page.tsx | 0 apps/web/app/auth/options.ts | 33 ++++----- .../{orgs/[orgslug] => auth}/reset/page.tsx | 0 .../{orgs/[orgslug] => auth}/reset/reset.tsx | 0 .../signup/InviteOnlySignUp.tsx | 0 .../[orgslug] => auth}/signup/OpenSignup.tsx | 0 .../{orgs/[orgslug] => auth}/signup/page.tsx | 10 +-- .../[orgslug] => auth}/signup/signup.tsx | 4 +- apps/web/app/home/home.tsx | 57 +++++++++++++++ apps/web/app/home/page.tsx | 16 ++++ .../components/Contexts/LHSessionContext.tsx | 2 - apps/web/components/Contexts/OrgContext.tsx | 73 +++++++++---------- .../components/StyledElements/Error/Error.tsx | 20 ++++- .../components/StyledElements/Info/Info.tsx | 37 ++++++++++ apps/web/middleware.ts | 17 ++++- 20 files changed, 217 insertions(+), 141 deletions(-) delete mode 100644 apps/web/app/auth/cookies.ts rename apps/web/app/{orgs/[orgslug] => auth}/forgot/forgot.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/forgot/page.tsx (100%) create mode 100644 apps/web/app/auth/layout.tsx rename apps/web/app/{ => auth}/login/login.tsx (100%) rename apps/web/app/{ => auth}/login/page.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/reset/page.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/reset/reset.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/signup/InviteOnlySignUp.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/signup/OpenSignup.tsx (100%) rename apps/web/app/{orgs/[orgslug] => auth}/signup/page.tsx (82%) rename apps/web/app/{orgs/[orgslug] => auth}/signup/signup.tsx (98%) create mode 100644 apps/web/app/home/home.tsx create mode 100644 apps/web/app/home/page.tsx create mode 100644 apps/web/components/StyledElements/Info/Info.tsx diff --git a/apps/web/app/auth/cookies.ts b/apps/web/app/auth/cookies.ts deleted file mode 100644 index 7c461f05..00000000 --- a/apps/web/app/auth/cookies.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { LEARNHOUSE_TOP_DOMAIN } from '@services/config/config' - -const cookiePrefix = '__LRN-' -const cookieDomain = - LEARNHOUSE_TOP_DOMAIN == `.${LEARNHOUSE_TOP_DOMAIN}` -const cookieSecure = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : true -const cookieSameSite = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? 'lax' : 'None' - -export const cookiesOptions = { - sessionToken: { - name: `__Secure-next-auth.session-token`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, - callbackUrl: { - name: `__Secure-next-auth.callback-url`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, - csrfToken: { - name: `__Host-next-auth.csrf-token`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, - pkceCodeVerifier: { - name: `${cookiePrefix}next-auth.pkce.code_verifier`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, - state: { - name: `${cookiePrefix}next-auth.state`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, - nonce: { - name: `${cookiePrefix}next-auth.nonce`, - options: { - domain: cookieDomain, - httpOnly: true, - sameSite: cookieSameSite, - path: '/', - secure: cookieSecure, - }, - }, -} diff --git a/apps/web/app/orgs/[orgslug]/forgot/forgot.tsx b/apps/web/app/auth/forgot/forgot.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/forgot/forgot.tsx rename to apps/web/app/auth/forgot/forgot.tsx diff --git a/apps/web/app/orgs/[orgslug]/forgot/page.tsx b/apps/web/app/auth/forgot/page.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/forgot/page.tsx rename to apps/web/app/auth/forgot/page.tsx diff --git a/apps/web/app/auth/layout.tsx b/apps/web/app/auth/layout.tsx new file mode 100644 index 00000000..37fbd3a3 --- /dev/null +++ b/apps/web/app/auth/layout.tsx @@ -0,0 +1,19 @@ +'use client' +import { OrgProvider } from '@components/Contexts/OrgContext' +import ErrorUI from '@components/StyledElements/Error/Error' +import { useSearchParams } from 'next/navigation' + + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode +}) { + const searchParams = useSearchParams() + const orgslug = searchParams.get('orgslug') + if (orgslug) { + return {children} + } else { + return + } +} \ No newline at end of file diff --git a/apps/web/app/login/login.tsx b/apps/web/app/auth/login/login.tsx similarity index 100% rename from apps/web/app/login/login.tsx rename to apps/web/app/auth/login/login.tsx diff --git a/apps/web/app/login/page.tsx b/apps/web/app/auth/login/page.tsx similarity index 100% rename from apps/web/app/login/page.tsx rename to apps/web/app/auth/login/page.tsx diff --git a/apps/web/app/auth/options.ts b/apps/web/app/auth/options.ts index 9a7e751d..04553622 100644 --- a/apps/web/app/auth/options.ts +++ b/apps/web/app/auth/options.ts @@ -8,7 +8,6 @@ import { LEARNHOUSE_TOP_DOMAIN, getUriWithOrg } from '@services/config/config' import { getResponseMetadata } from '@services/utils/ts/requests' import CredentialsProvider from 'next-auth/providers/credentials' import GoogleProvider from 'next-auth/providers/google' -import { cookiesOptions } from './cookies' const isDevEnv = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : false @@ -82,24 +81,24 @@ export const nextAuthOptions = { token.user = userFromOAuth.data } - // Refresh token - // TODO : Improve this implementation - if (token?.user?.tokens) { - const RefreshedToken = await getNewAccessTokenUsingRefreshTokenServer( - token?.user?.tokens?.refresh_token - ) - token = { - ...token, - user: { - ...token.user, - tokens: { - ...token.user.tokens, - access_token: RefreshedToken.access_token, + // Refresh token + // TODO : Improve this implementation + if (token?.user?.tokens) { + const RefreshedToken = await getNewAccessTokenUsingRefreshTokenServer( + token?.user?.tokens?.refresh_token + ) + token = { + ...token, + user: { + ...token.user, + tokens: { + ...token.user.tokens, + access_token: RefreshedToken.access_token, + }, }, - }, + } } - } - return token + return token }, async session({ session, token }: any) { // Include user information in the session diff --git a/apps/web/app/orgs/[orgslug]/reset/page.tsx b/apps/web/app/auth/reset/page.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/reset/page.tsx rename to apps/web/app/auth/reset/page.tsx diff --git a/apps/web/app/orgs/[orgslug]/reset/reset.tsx b/apps/web/app/auth/reset/reset.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/reset/reset.tsx rename to apps/web/app/auth/reset/reset.tsx diff --git a/apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx b/apps/web/app/auth/signup/InviteOnlySignUp.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/signup/InviteOnlySignUp.tsx rename to apps/web/app/auth/signup/InviteOnlySignUp.tsx diff --git a/apps/web/app/orgs/[orgslug]/signup/OpenSignup.tsx b/apps/web/app/auth/signup/OpenSignup.tsx similarity index 100% rename from apps/web/app/orgs/[orgslug]/signup/OpenSignup.tsx rename to apps/web/app/auth/signup/OpenSignup.tsx diff --git a/apps/web/app/orgs/[orgslug]/signup/page.tsx b/apps/web/app/auth/signup/page.tsx similarity index 82% rename from apps/web/app/orgs/[orgslug]/signup/page.tsx rename to apps/web/app/auth/signup/page.tsx index 9a9b48e4..babc3203 100644 --- a/apps/web/app/orgs/[orgslug]/signup/page.tsx +++ b/apps/web/app/auth/signup/page.tsx @@ -9,10 +9,10 @@ type MetadataProps = { searchParams: { [key: string]: string | string[] | undefined } } -export async function generateMetadata({ - params, -}: MetadataProps): Promise { - const orgslug = params.orgslug +export async function generateMetadata( + params + : MetadataProps): Promise { + const orgslug = params.searchParams.orgslug // Get Org context information const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, @@ -25,7 +25,7 @@ export async function generateMetadata({ } const SignUp = async (params: any) => { - const orgslug = params.params.orgslug + const orgslug = params.searchParams.orgslug const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, tags: ['organizations'], diff --git a/apps/web/app/orgs/[orgslug]/signup/signup.tsx b/apps/web/app/auth/signup/signup.tsx similarity index 98% rename from apps/web/app/orgs/[orgslug]/signup/signup.tsx rename to apps/web/app/auth/signup/signup.tsx index 9eb87d4a..3eb1b2e2 100644 --- a/apps/web/app/orgs/[orgslug]/signup/signup.tsx +++ b/apps/web/app/auth/signup/signup.tsx @@ -69,7 +69,7 @@ function SignUpClient(props: SignUpClientProps) { props.org.org_uuid, props.org?.logo_image )}`} - alt="Learnhouse" + alt="LearnHouse" style={{ width: 'auto', height: 70 }} className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white" /> @@ -161,7 +161,7 @@ const NoTokenScreen = (props: any) => { "Invite code is valid, you'll be redirected to the signup page in a few seconds" ) setTimeout(() => { - router.push(`/signup?inviteCode=${inviteCode}`) + router.push(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`) }, 2000) } else { toast.error('Invite code is invalid') diff --git a/apps/web/app/home/home.tsx b/apps/web/app/home/home.tsx new file mode 100644 index 00000000..6c5c83ae --- /dev/null +++ b/apps/web/app/home/home.tsx @@ -0,0 +1,57 @@ +'use client' +import { useLHSession } from '@components/Contexts/LHSessionContext' +import UserAvatar from '@components/Objects/UserAvatar'; +import { getAPIUrl, getUriWithOrg, getUriWithoutOrg } from '@services/config/config'; +import { swrFetcher } from '@services/utils/ts/requests'; +import { ArrowRightCircle, Info } from 'lucide-react'; +import { signOut } from 'next-auth/react'; +import Image from 'next/image'; +import Link from 'next/link'; +import learnhouseIcon from 'public/learnhouse_bigicon_1.png' +import React, { useEffect } from 'react' +import useSWR from 'swr'; + +function HomeClient() { + const session = useLHSession() as any; + const access_token = session?.data?.tokens?.access_token; + const { data: orgs } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, (url) => swrFetcher(url, access_token)) + + useEffect(() => { + console.log(orgs) + + + }, [session, orgs]) + return ( +
+ +
+ +
+ +
Hello, {session?.data?.user.first_name} {session?.data?.user.last_name}
+
Your Organizations
+ {orgs && orgs.length == 0 &&
+ + It seems you're not part of an organization yet, join one to be able to see it here +
} +
+ {orgs && orgs.map((org: any) => ( + +
{org.name}
+ + + ))} +
+
signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/') })}>Sign out
+ +
+ ) +} + +export default HomeClient \ No newline at end of file diff --git a/apps/web/app/home/page.tsx b/apps/web/app/home/page.tsx new file mode 100644 index 00000000..2dd1b5ef --- /dev/null +++ b/apps/web/app/home/page.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import HomeClient from './home' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Home', +} +function Home() { + return ( +
+ +
+ ) +} + +export default Home \ No newline at end of file diff --git a/apps/web/components/Contexts/LHSessionContext.tsx b/apps/web/components/Contexts/LHSessionContext.tsx index d60413eb..90acb9d8 100644 --- a/apps/web/components/Contexts/LHSessionContext.tsx +++ b/apps/web/components/Contexts/LHSessionContext.tsx @@ -9,7 +9,6 @@ function LHSessionProvider({ children }: { children: React.ReactNode }) { const session = useSession(); useEffect(() => { - console.log('useLHSession', session); }, []) @@ -20,7 +19,6 @@ function LHSessionProvider({ children }: { children: React.ReactNode }) { else if (session) { return ( - {console.log('rendered')} {children} ) diff --git a/apps/web/components/Contexts/OrgContext.tsx b/apps/web/components/Contexts/OrgContext.tsx index b2e77f3f..d20726ff 100644 --- a/apps/web/components/Contexts/OrgContext.tsx +++ b/apps/web/components/Contexts/OrgContext.tsx @@ -1,51 +1,48 @@ 'use client' -import { getAPIUrl } from '@services/config/config' +import { getAPIUrl, getUriWithoutOrg } from '@services/config/config' import { swrFetcher } from '@services/utils/ts/requests' -import React, { useContext, useEffect, useState } from 'react' +import React, { createContext, useContext, useEffect, useMemo, useState } from 'react' import useSWR from 'swr' -import { createContext } from 'react' -import { useRouter } from 'next/navigation' import { useLHSession } from '@components/Contexts/LHSessionContext' import ErrorUI from '@components/StyledElements/Error/Error' +import InfoUI from '@components/StyledElements/Info/Info' +import { usePathname } from 'next/navigation' -export const OrgContext = createContext({}) as any +export const OrgContext = createContext(null) -export function OrgProvider({ - children, - orgslug, -}: { - children: React.ReactNode - orgslug: string -}) { - const session = useLHSession() as any; - const access_token = session?.data?.tokens?.access_token; - const { data: org } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, (url) => swrFetcher(url, access_token)) - const [isLoading, setIsLoading] = useState(true); - const [isOrgActive, setIsOrgActive] = useState(true); +export function OrgProvider({ children, orgslug }: { children: React.ReactNode, orgslug: string }) { + const session = useLHSession() as any + const pathname = usePathname() + const accessToken = session?.data?.tokens?.access_token + const isAllowedPathname = ['/login', '/signup'].includes(pathname); - // Check if Org is Active - const verifyIfOrgIsActive = () => { - if (org && org?.config.config.GeneralConfig.active === false) { - setIsOrgActive(false) - } - else { - setIsOrgActive(true) - } + const { data: org, error: orgError } = useSWR( + `${getAPIUrl()}orgs/slug/${orgslug}`, + (url) => swrFetcher(url, accessToken) + ) + const { data: orgs, error: orgsError } = useSWR( + `${getAPIUrl()}orgs/user/page/1/limit/10`, + (url) => swrFetcher(url, accessToken) + ) + + + const isOrgActive = useMemo(() => org?.config?.config?.GeneralConfig?.active !== false, [org]) + const isUserPartOfTheOrg = useMemo(() => orgs?.some((userOrg: any) => userOrg.id === org?.id), [orgs, org?.id]) + + if (orgError || orgsError) return + if (!org || !orgs || !session) return
Loading...
+ if (!isOrgActive) return + if (!isUserPartOfTheOrg && session.status == 'authenticated' && !isAllowedPathname) { + return ( + + ) } - useEffect(() => { - if (org && session) { - verifyIfOrgIsActive() - setIsLoading(false) - } - }, [org, session]) - - if (!isLoading) { - return {children} - } - if (!isOrgActive) { - return - } + return {children} } export function useOrg() { diff --git a/apps/web/components/StyledElements/Error/Error.tsx b/apps/web/components/StyledElements/Error/Error.tsx index ef923c0a..ee1b0812 100644 --- a/apps/web/components/StyledElements/Error/Error.tsx +++ b/apps/web/components/StyledElements/Error/Error.tsx @@ -1,9 +1,11 @@ 'use client' -import { AlertTriangle, RefreshCcw } from 'lucide-react' +import { getUriWithoutOrg } from '@services/config/config' +import { AlertTriangle, HomeIcon, RefreshCcw } from 'lucide-react' +import Link from 'next/link' import { useRouter } from 'next/navigation' import React from 'react' -function ErrorUI(params: { message?: string }) { +function ErrorUI(params: { message?: string, submessage?: string }) { const router = useRouter() function reloadPage() { @@ -15,9 +17,12 @@ function ErrorUI(params: { message?: string }) {
-

{params.message ? params.message : 'Something went wrong'}

+
+

{params.message ? params.message : 'Something went wrong'}

+

{params.submessage ? params.submessage : ''}

+
-
+
+ + + Home +
) diff --git a/apps/web/components/StyledElements/Info/Info.tsx b/apps/web/components/StyledElements/Info/Info.tsx new file mode 100644 index 00000000..45492486 --- /dev/null +++ b/apps/web/components/StyledElements/Info/Info.tsx @@ -0,0 +1,37 @@ +'use client' +import { getUriWithoutOrg } from '@services/config/config' +import { AlertTriangle, Diamond, Home, PersonStanding, RefreshCcw } from 'lucide-react' +import Link from 'next/link' +import React from 'react' + +function InfoUI(params: { message?: string, submessage?: string, cta?: string, href: string }) { + return ( +
+
+ +
+

{params.message ? params.message : 'Something went wrong'}

+

{params.submessage ? params.submessage : ''}

+
+
+ {params.cta &&
+ + + {params.cta} + + + + Home + +
} +
+ ) +} + +export default InfoUI diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index e32b7a01..47327727 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -35,12 +35,24 @@ export default async function middleware(req: NextRequest) { ? fullhost.replace(`.${LEARNHOUSE_DOMAIN}`, '') : (default_org as string) + // Out of orgslug paths & rewrite + const standard_paths = ['/home'] + const auth_paths = ['/login', '/signup', '/reset'] + if (standard_paths.includes(pathname)) { + // Redirect to the same pathname with the original search params + return NextResponse.rewrite(new URL(`${pathname}${search}`, req.url)) + } + if (auth_paths.includes(pathname)) { + // Redirect to the same pathname with the original search params + return NextResponse.rewrite(new URL(`/auth${pathname}${search}`, req.url)) + } + // Login if (orgslug == 'auth' || pathname.startsWith('/login')) { return NextResponse.rewrite(new URL(`/login${search}`, req.url)) } - // Install Page + // Install Page (depreceated) if (pathname.startsWith('/install')) { // Check if install mode is enabled const install_mode = await isInstallModeEnabled() @@ -71,8 +83,7 @@ export default async function middleware(req: NextRequest) { redirectUrl.search = queryString } return NextResponse.redirect(redirectUrl) - } else{ - + } else { } }