Skip to content

Commit

Permalink
add support for user login #94
Browse files Browse the repository at this point in the history
add support for user login
  • Loading branch information
yesyash authored Aug 4, 2024
2 parents e216e1c + 222f591 commit bda36b9
Show file tree
Hide file tree
Showing 21 changed files with 938 additions and 329 deletions.
28 changes: 13 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,34 @@
"test-watch": "jest --watch "
},
"dependencies": {
"@headlessui/react": "^1.7.16",
"@heroicons/react": "^2.1.5",
"@tanstack/react-query": "^4.33.0",
"@tanstack/react-query-devtools": "^4.33.0",
"@tanstack/react-query": "^5.51.15",
"@tanstack/react-query-devtools": "^5.51.15",
"@testing-library/react-hooks": "^8.0.1",
"autoprefixer": "10.4.14",
"axios": "^1.6.7",
"axios": "^1.7.2",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
"next": "13.4.4",
"next": "^14.2.4",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^4.10.1",
"react-test-renderer": "^18.2.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4",
"zod": "^3.23.8"
"typescript": "^5.5.3",
"zod": "^3.23.8",
"zustand": "^4.5.4"
},
"devDependencies": {
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.3",
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"eslint-config-next": "^14.2.4",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
Expand All @@ -63,7 +61,7 @@
"local-ssl-proxy": "^2.0.5",
"msw": "^1.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"ts-jest": "^29.1.1"
},
Expand Down
1 change: 1 addition & 0 deletions src/api/rds/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RdsApi } from "./rds.api"
9 changes: 9 additions & 0 deletions src/api/rds/rds.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { rdsClient } from "@/utils/client"
import { RdsUserSelfResDto } from "./rds.dto"

export class RdsApi {
public static async getCurrentUserData(): Promise<RdsUserSelfResDto> {
const { data } = await rdsClient.get("/users/self")
return data
}
}
33 changes: 33 additions & 0 deletions src/api/rds/rds.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export type RdsUserSelfResDto = {
id: string
incompleteUserDetails: boolean
discordJoinedAt: string
discordId: string
roles: {
archived: boolean
in_discord: boolean
member: boolean
}
linkedin_id: boolean
picture?: {
url: string
publicId: string
}
yoe: number
github_created_at: number
github_display_name: string
github_id: string
twitter_id: string
username: string
github_user_id: string
first_name: string
profileURL: string
website: string
last_name: string
company: string
designation: string
instagram_id: string
profileStatus: string // TODO: Make this a enum
updated_at: number
created_at: number
}
45 changes: 45 additions & 0 deletions src/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { config } from "@/config"
import Link from "next/link"
import { SignInWithRds } from "./signin-with-rds"

type NavLinksList = {
className?: string
children: React.ReactNode
}

const NavLinksList = ({ className, children }: NavLinksList) => {
return <ul className={className}>{children}</ul>
}

type NavLinkProps = {
href: string
children: React.ReactNode
}

const NavLink = ({ href, children }: NavLinkProps) => {
return (
<li className="rounded-full px-4 py-1 transition-all ease-in-out hover:bg-gray-100">
<Link href={href}>{children}</Link>
</li>
)
}

export const Navbar = () => {
return (
<nav className="fixed left-0 top-0 w-full bg-white">
<div className="mx-auto flex h-14 w-full max-w-screen-2xl items-center gap-6 px-6">
<h1 className="text-xl font-bold">Rds</h1>

<NavLinksList className="flex items-center">
<NavLink href={config.welcomeSiteUrl}>Welcome</NavLink>
<NavLink href={config.membersSiteUrl}>Members</NavLink>
<NavLink href={config.membersSiteUrl}>Status</NavLink>
</NavLinksList>

<div className="ml-auto">
<SignInWithRds />
</div>
</div>
</nav>
)
}
43 changes: 43 additions & 0 deletions src/components/signin-with-rds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { config } from "@/config"
import { ROUTES } from "@/routes"
import { useGlobalStore } from "@/store/global-store"
import Image from "next/image"
import { useRouter } from "next/navigation"

export const SignInWithRds = () => {
const { push } = useRouter()
const { user } = useGlobalStore((store) => ({ user: store.user }))

const handleSignIn = async () => {
const redirectUrl = `${config.skillTreeUrl}${ROUTES.requests}`
const url = `${config.rdsBackendBaseUrl}/auth/github/login?redirectURL=${redirectUrl}?v2=true`

push(url)
}

// TODO : make a separate component for this
if (user) {
return (
<div className="flex items-center gap-2">
<Image
src={user.profilePicture}
alt={user.name}
width={64}
height={64}
className="h-7 w-7 overflow-hidden rounded-full"
/>

<span className="inline-block">{user.name}</span>
</div>
)
}

return (
<button
onClick={handleSignIn}
className="h-9 rounded-lg bg-gray-200 px-4 font-medium text-gray-700 transition hover:bg-gray-300"
>
SignIn within rds
</button>
)
}
51 changes: 51 additions & 0 deletions src/components/user-guard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { RdsApi } from "@/api/rds"
import { ROUTES } from "@/routes"
import { TGlobalStoreUser, useGlobalStore } from "@/store/global-store"
import { useQuery } from "@tanstack/react-query"
import { usePathname } from "next/navigation"
import { useEffect } from "react"

type Props = {
children: React.ReactNode
}

const UNPROTECTED_PATHS = [ROUTES.root, ROUTES.signIn]

export const UserGuard = ({ children }: Props) => {
const pathname = usePathname()
const isUnprotectedPath = UNPROTECTED_PATHS.includes(pathname)
const { setGlobalStore } = useGlobalStore((store) => ({ setGlobalStore: store.setGlobalStore }))

const { data, isLoading, isError } = useQuery({
enabled: !isUnprotectedPath,
queryKey: ["RdsApi.getCurrentUserData"],
queryFn: RdsApi.getCurrentUserData,
})

useEffect(() => {
if (!data) {
return
}

const firstName = data?.first_name ?? ""
const lastName = data?.last_name ?? ""

const user: TGlobalStoreUser = {
id: data.id,
name: firstName + " " + lastName,
profilePicture: data?.picture?.url ?? "",
}

setGlobalStore({ user })
}, [data])

if (isLoading) {
return <div>Loading...</div>
}

if (isError) {
return <div>Error...</div>
}

return <>{children}</>
}
16 changes: 8 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod"

const configSchema = z.object({
appEnv: z.enum(["dev", "staging", "prod"]),
Expand All @@ -10,9 +10,9 @@ const configSchema = z.object({
welcomeSiteUrl: z.string(),
wwwSiteUrl: z.string(),
mySiteUrl: z.string(),
});
})

type TConfig = z.infer<typeof configSchema>;
type TConfig = z.infer<typeof configSchema>

export const config = {
appEnv: process.env.NEXT_PUBLIC_APP_ENV,
Expand All @@ -24,16 +24,16 @@ export const config = {
welcomeSiteUrl: process.env.NEXT_PUBLIC_WELCOME_SITE_URL,
wwwSiteUrl: process.env.NEXT_PUBLIC_WWW_SITE_URL,
mySiteUrl: process.env.NEXT_PUBLIC_MY_SITE_URL,
} as TConfig;
} as TConfig

/**
* Validate if all the required environment variables defined in the schema above are set
* and are in the correct format else throw an error
* ---
*/
export const validateEnv = () => {
const result = configSchema.safeParse(config);
const errors = result.error?.flatten().fieldErrors;
const result = configSchema.safeParse(config)
const errors = result.error?.flatten().fieldErrors

if (!result.success) {
throw new Error(
Expand All @@ -45,6 +45,6 @@ export const validateEnv = () => {
null,
2
)
);
)
}
};
}
1 change: 1 addition & 0 deletions src/modules/requests/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Requests } from "./requests"
13 changes: 13 additions & 0 deletions src/modules/requests/requests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Navbar } from "@/components/navbar"

export const Requests = () => {
return (
<div>
<Navbar />

<main className="mx-auto mt-14 max-w-screen-2xl p-6">
<h1 className="pb-6">Requests</h1>
</main>
</div>
)
}
39 changes: 13 additions & 26 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
import type { ReactElement, ReactNode } from "react";
import type { NextPage } from "next";
import type { AppProps } from "next/app";

import { validateEnv } from "@/config";
import "@/styles/global.css";
import { Providers } from "@/utils/providers";
import type { AppProps } from "next/app"

import { validateEnv } from "@/config"
import "@/styles/global.css"
import { Providers } from "@/utils/providers"
import { UserGuard } from "@/components/user-guard"

/**
* Validate if all the required environment variables are set
* this will allow us to fail fast if any of the required environment variables are not set
* ---
*/
validateEnv();


export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}

export default function App({ Component, pageProps }: AppPropsWithLayout) {
// For more info on this pattern visit: https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#with-typescript
const getLayout = Component.getLayout ?? ((page) => page);
* ---
*/
validateEnv()

export default function MyApp({ Component, pageProps }: AppProps) {
return (
<Providers>
{/* @ts-ignore */}
{getLayout(<Component {...pageProps} />)}
<UserGuard>
<Component {...pageProps} />
</UserGuard>
</Providers>
);
)
}
22 changes: 16 additions & 6 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
export default function Home() {
import { ROUTES } from "@/routes"
import Link from "next/link"

const Homepage = () => {
return (
<div>
<div>
Homepage
</div>
<div className="flex h-screen w-screen flex-col items-center justify-center">
<h1 className="pb-1 text-4xl font-bold">Welcome to Skilltree</h1>
<p className="text-sm text-gray-500">
Visit{" "}
<Link className="text-blue-500 hover:text-blue-600 hover:underline" href={ROUTES.requests}>
/requests
</Link>{" "}
to view all pending skill requests
</p>
</div>
);
)
}

export default Homepage
3 changes: 3 additions & 0 deletions src/pages/requests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Requests } from "@/modules/requests"

export default Requests
Loading

0 comments on commit bda36b9

Please sign in to comment.