Merge pull request #260 from learnhouse/feat/use-next-auth

New Auth Mechanism + Google OAuth
This commit is contained in:
Badr B 2024-06-07 15:12:21 +01:00 committed by GitHub
commit ddbd413539
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
140 changed files with 2833 additions and 1919 deletions

1
.npmrc
View file

@ -1 +1,2 @@
shared-workspace-lockfile=false shared-workspace-lockfile=false
package-manager-strict=false

View file

@ -1,11 +1,14 @@
from datetime import timedelta from datetime import timedelta
from typing import Literal, Optional
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from sqlmodel import Session from sqlmodel import Session
from src.db.users import UserRead from src.db.users import AnonymousUser, UserRead
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
from src.security.auth import AuthJWT, authenticate_user from src.security.auth import AuthJWT, authenticate_user, get_current_user
from src.services.auth.utils import signWithGoogle
router = APIRouter() router = APIRouter()
@ -74,6 +77,58 @@ async def login(
return result return result
class ThirdPartyLogin(BaseModel):
email: EmailStr
provider: Literal["google"]
access_token: str
@router.post("/oauth")
async def third_party_login(
request: Request,
response: Response,
body: ThirdPartyLogin,
org_id: Optional[int] = None,
current_user: AnonymousUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
Authorize: AuthJWT = Depends(),
):
# Google
if body.provider == "google":
user = await signWithGoogle(
request, body.access_token, body.email, org_id, current_user, db_session
)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect Email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = Authorize.create_access_token(subject=user.email)
refresh_token = Authorize.create_refresh_token(subject=user.email)
Authorize.set_refresh_cookies(refresh_token)
# set cookies using fastapi
response.set_cookie(
key="access_token_cookie",
value=access_token,
httponly=False,
domain=get_learnhouse_config().hosting_config.cookie_config.domain,
expires=int(timedelta(hours=8).total_seconds()),
)
user = UserRead.model_validate(user)
result = {
"user": user,
"tokens": {"access_token": access_token, "refresh_token": refresh_token},
}
return result
@router.delete("/logout") @router.delete("/logout")
def logout(Authorize: AuthJWT = Depends()): def logout(Authorize: AuthJWT = Depends()):
""" """

View file

@ -8,6 +8,7 @@ from src.services.orgs.invites import (
get_invite_code, get_invite_code,
get_invite_codes, get_invite_codes,
) )
from src.services.orgs.join import JoinOrg, join_org
from src.services.orgs.users import ( from src.services.orgs.users import (
get_list_of_invited_users, get_list_of_invited_users,
get_organization_users, get_organization_users,
@ -99,6 +100,19 @@ async def api_get_org_users(
return await get_organization_users(request, org_id, db_session, current_user) return await get_organization_users(request, org_id, db_session, current_user)
@router.post("/join")
async def api_join_an_org(
request: Request,
args: JoinOrg,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
"""
Get single Org by ID
"""
return await join_org(request, args, current_user, db_session)
@router.put("/{org_id}/users/{user_id}/role/{role_uuid}") @router.put("/{org_id}/users/{user_id}/role/{role_uuid}")
async def api_update_user_role( async def api_update_user_role(
request: Request, request: Request,

View file

@ -85,7 +85,6 @@ async def api_create_user_with_orgid(
""" """
Create User with Org ID Create User with Org ID
""" """
print(await get_org_join_mechanism(request, org_id, current_user, db_session))
# TODO(fix) : This is temporary, logic should be moved to service # TODO(fix) : This is temporary, logic should be moved to service
if ( if (

View file

@ -0,0 +1,70 @@
import random
from typing import Optional
from fastapi import Depends, HTTPException, Request
import httpx
from sqlmodel import Session, select
from src.core.events.database import get_db_session
from src.db.users import User, UserCreate, UserRead
from src.security.auth import get_current_user
from src.services.users.users import create_user, create_user_without_org
async def get_google_user_info(access_token: str):
url = "https://www.googleapis.com/oauth2/v3/userinfo"
headers = {"Authorization": f"Bearer {access_token}"}
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail="Failed to fetch user info from Google",
)
return response.json()
async def signWithGoogle(
request: Request,
access_token: str,
email: str,
org_id: Optional[int] = None,
current_user=Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
# Google
google_user = await get_google_user_info(access_token)
user = db_session.exec(
select(User).where(User.email == google_user["email"])
).first()
if not user:
username = (
google_user["given_name"]
+ google_user["family_name"]
+ str(random.randint(10, 99))
)
user_object = UserCreate(
email=google_user["email"],
username=username,
password="",
first_name=google_user["given_name"],
last_name=google_user["family_name"],
avatar_image=google_user["picture"],
)
if org_id is not None:
user = await create_user(
request, db_session, current_user, user_object, org_id
)
return user
else:
user = await create_user_without_org(
request, db_session, current_user, user_object
)
return user
return UserRead.model_validate(user)

View file

@ -0,0 +1,119 @@
from datetime import datetime
from typing import Optional
from fastapi import HTTPException, Request
from pydantic import BaseModel
from sqlmodel import Session, select
from src.db.organizations import Organization
from src.db.user_organizations import UserOrganization
from src.db.users import AnonymousUser, PublicUser, User
from src.services.orgs.invites import get_invite_code
from src.services.orgs.orgs import get_org_join_mechanism
class JoinOrg(BaseModel):
org_id: int
user_id: str
invite_code: Optional[str]
async def join_org(
request: Request,
args: JoinOrg,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
statement = select(Organization).where(Organization.id == args.org_id)
result = db_session.exec(statement)
org = result.first()
if not org:
raise HTTPException(
status_code=404,
detail="Organization not found",
)
join_method = await get_org_join_mechanism(
request, args.org_id, current_user, db_session
)
# Get User
statement = select(User).where(User.id == args.user_id)
result = db_session.exec(statement)
user = result.first()
# Check if User isn't already part of the org
statement = select(UserOrganization).where(
UserOrganization.user_id == args.user_id, UserOrganization.org_id == args.org_id
)
result = db_session.exec(statement)
userorg = result.first()
if userorg:
raise HTTPException(
status_code=400, detail="User is already part of that organization"
)
if join_method == "inviteOnly" and user and org and args.invite_code:
if user.id is not None and org.id is not None:
# Check if invite code exists
inviteCode = await get_invite_code(
request, org.id, args.invite_code, current_user, db_session
)
if not inviteCode:
raise HTTPException(
status_code=400,
detail="Invite code is incorrect",
)
# Link user and organization
user_organization = UserOrganization(
user_id=user.id,
org_id=org.id,
role_id=3,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(user_organization)
db_session.commit()
return "Great, You're part of the Organization"
else:
raise HTTPException(
status_code=403,
detail="Something wrong, try later.",
)
if join_method == "open" and user and org:
if user.id is not None and org.id is not None:
# Link user and organization
user_organization = UserOrganization(
user_id=user.id,
org_id=org.id,
role_id=3,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(user_organization)
db_session.commit()
return "Great, You're part of the Organization"
else:
raise HTTPException(
status_code=403,
detail="Something wrong, try later.",
)
else:
raise HTTPException(
status_code=403,
detail="Something wrong, try later.",
)

View file

@ -436,8 +436,7 @@ async def get_orgs_by_user(
orgs = result.all() orgs = result.all()
return orgs return orgs #type:ignore
# Config related # Config related
async def update_org_signup_mechanism( async def update_org_signup_mechanism(

View file

@ -17,7 +17,7 @@ def send_account_creation_email(
<body> <body>
<p>Hello {user.username}</p> <p>Hello {user.username}</p>
<p>Welcome to LearnHouse! , get started by creating your own organization or join a one.</p> <p>Welcome to LearnHouse! , get started by creating your own organization or join a one.</p>
<p>Need some help to get started ? <a href="https://learn.learnhouse.io">LearnHouse Academy</a></p> <p>Need some help to get started ? <a href="https://university.learnhouse.io">LearnHouse Academy</a></p>
</body> </body>
</html> </html>
""", """,

View file

@ -10,7 +10,7 @@ importers:
dependencies: dependencies:
'@hocuspocus/server': '@hocuspocus/server':
specifier: ^2.11.3 specifier: ^2.11.3
version: 2.11.3(y-protocols@1.0.6)(yjs@13.6.14) version: 2.11.3(y-protocols@1.0.6(yjs@13.6.14))(yjs@13.6.14)
bun: bun:
specifier: ^1.0.36 specifier: ^1.0.36
version: 1.1.1 version: 1.1.1
@ -80,7 +80,6 @@ packages:
bun@1.1.1: bun@1.1.1:
resolution: {integrity: sha512-gV90TkJgHvI50X9BoKQ3zVpPEY6YP0vqOww2uZmsOyckZSRlcFYWhXZwFj6PV8KCFINYs8VZ65m59U2RuFYfWw==} resolution: {integrity: sha512-gV90TkJgHvI50X9BoKQ3zVpPEY6YP0vqOww2uZmsOyckZSRlcFYWhXZwFj6PV8KCFINYs8VZ65m59U2RuFYfWw==}
cpu: [arm64, x64]
os: [darwin, linux, win32] os: [darwin, linux, win32]
hasBin: true hasBin: true
@ -133,7 +132,7 @@ snapshots:
dependencies: dependencies:
lib0: 0.2.93 lib0: 0.2.93
'@hocuspocus/server@2.11.3(y-protocols@1.0.6)(yjs@13.6.14)': '@hocuspocus/server@2.11.3(y-protocols@1.0.6(yjs@13.6.14))(yjs@13.6.14)':
dependencies: dependencies:
'@hocuspocus/common': 2.11.3 '@hocuspocus/common': 2.11.3
async-lock: 1.4.1 async-lock: 1.4.1

View file

@ -4,7 +4,8 @@
"react/no-unescaped-entities": "off", "react/no-unescaped-entities": "off",
"@next/next/no-page-custom-font": "off", "@next/next/no-page-custom-font": "off",
"@next/next/no-img-element": "off", "@next/next/no-img-element": "off",
"unused-imports/no-unused-imports": "warn" "unused-imports/no-unused-imports": "warn",
"no-console": "warn"
}, },
"plugins": ["unused-imports"] "plugins": ["unused-imports"]
} }

2
apps/web/.gitignore vendored
View file

@ -43,3 +43,5 @@ next.config.original.js
# Sentry Config File # Sentry Config File
.sentryclirc .sentryclirc
certificates

View file

@ -0,0 +1,6 @@
import NextAuth from 'next-auth'
import { nextAuthOptions } from 'app/auth/options'
const handler = NextAuth(nextAuthOptions)
export { handler as GET, handler as POST }

View file

@ -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 <OrgProvider orgslug={orgslug}>{children}</OrgProvider>
} else {
return <ErrorUI message='Organization not specified' submessage='Please access this page from an Organization' />
}
}

View file

@ -10,11 +10,12 @@ import * as Form from '@radix-ui/react-form'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import { getOrgLogoMediaDirectory } from '@services/media/media' import { getOrgLogoMediaDirectory } from '@services/media/media'
import React from 'react' import React from 'react'
import { loginAndGetToken } from '@services/auth/auth' import { AlertTriangle, UserRoundPlus } from 'lucide-react'
import { AlertTriangle } from 'lucide-react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import Link from 'next/link' import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { signIn } from "next-auth/react"
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
import { useLHSession } from '@components/Contexts/LHSessionContext'
interface LoginClientProps { interface LoginClientProps {
org: any org: any
@ -40,7 +41,9 @@ const validate = (values: any) => {
const LoginClient = (props: LoginClientProps) => { const LoginClient = (props: LoginClientProps) => {
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
const router = useRouter() const router = useRouter();
const session = useLHSession() as any;
const [error, setError] = React.useState('') const [error, setError] = React.useState('')
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
@ -50,25 +53,25 @@ const LoginClient = (props: LoginClientProps) => {
validate, validate,
onSubmit: async (values) => { onSubmit: async (values) => {
setIsSubmitting(true) setIsSubmitting(true)
let res = await loginAndGetToken(values.email, values.password) const res = await signIn('credentials', {
let message = await res.json() redirect: false,
if (res.status == 200) { email: values.email,
router.push(`/`) password: values.password,
setIsSubmitting(false) callbackUrl: '/redirect_from_auth'
} else if ( });
res.status == 401 || if (res && res.error) {
res.status == 400 || setError("Wrong Email or password");
res.status == 404 || setIsSubmitting(false);
res.status == 409
) {
setError(message.detail)
setIsSubmitting(false)
} else { } else {
setError('Something went wrong') await signIn('credentials', {
setIsSubmitting(false) email: values.email,
password: values.password,
callbackUrl: '/redirect_from_auth'
});
} }
}, },
}) })
return ( return (
<div className="grid grid-flow-col justify-stretch h-screen"> <div className="grid grid-flow-col justify-stretch h-screen">
<div <div
@ -165,7 +168,6 @@ const LoginClient = (props: LoginClientProps) => {
Forgot password? Forgot password?
</Link> </Link>
</div> </div>
<div className="flex py-4"> <div className="flex py-4">
<Form.Submit asChild> <Form.Submit asChild>
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer"> <button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer">
@ -174,6 +176,18 @@ const LoginClient = (props: LoginClientProps) => {
</Form.Submit> </Form.Submit>
</div> </div>
</FormLayout> </FormLayout>
<div className='flex h-0.5 rounded-2xl bg-slate-100 mt-5 mx-10'></div>
<div className='flex justify-center py-5 mx-auto'>OR </div>
<div className='flex flex-col space-y-4'>
<Link href={{ pathname: getUriWithoutOrg('/signup'), query: props.org.slug ? { orgslug: props.org.slug } : null }} className="flex justify-center items-center py-3 text-md w-full bg-gray-800 text-gray-300 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<UserRoundPlus size={17} />
<span>Sign up</span>
</Link>
<button onClick={() => signIn('google', { callbackUrl: '/redirect_from_auth' })} className="flex justify-center py-3 text-md w-full bg-white text-slate-600 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<img src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" alt="" />
<span>Sign in with Google</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -3,14 +3,14 @@ import LoginClient from './login'
import { Metadata } from 'next' import { Metadata } from 'next'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string; courseid: string } params: { orgslug: string }
searchParams: { [key: string]: string | string[] | undefined } searchParams: { [key: string]: string | string[] | undefined }
} }
export async function generateMetadata({ export async function generateMetadata(params: MetadataProps): Promise<Metadata> {
params, const orgslug = params.searchParams.orgslug
}: MetadataProps): Promise<Metadata> {
const orgslug = params.orgslug //const orgslug = params.orgslug
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(orgslug, { const org = await getOrganizationContextInfo(orgslug, {
revalidate: 0, revalidate: 0,
@ -22,8 +22,8 @@ export async function generateMetadata({
} }
} }
const Login = async (params: any) => { const Login = async (params: MetadataProps) => {
const orgslug = params.params.orgslug const orgslug = params.searchParams.orgslug
const org = await getOrganizationContextInfo(orgslug, { const org = await getOrganizationContextInfo(orgslug, {
revalidate: 0, revalidate: 0,
tags: ['organizations'], tags: ['organizations'],

View file

@ -0,0 +1,114 @@
import {
getNewAccessTokenUsingRefreshTokenServer,
getUserSession,
loginAndGetToken,
loginWithOAuthToken,
} from '@services/auth/auth'
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'
const isDevEnv = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : false
export const nextAuthOptions = {
debug: true,
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Credentials',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
email: { label: 'Email', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
// logic to verify if user exists
let unsanitized_req = await loginAndGetToken(
credentials?.email,
credentials?.password
)
let res = await getResponseMetadata(unsanitized_req)
if (res.success) {
// If login failed, then this is the place you could do a registration
return res.data
} else {
return null
}
},
}),
GoogleProvider({
clientId: process.env.LEARNHOUSE_GOOGLE_CLIENT_ID || '',
clientSecret: process.env.LEARNHOUSE_GOOGLE_CLIENT_SECRET || '',
}),
],
pages: {
signIn: getUriWithOrg('auth', '/'),
verifyRequest: getUriWithOrg('auth', '/'),
error: getUriWithOrg('auth', '/'), // Error code passed in query string as ?error=
},
cookies: {
sessionToken: {
name: `${!isDevEnv ? '__Secure-' : ''}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
// When working on localhost, the cookie domain must be omitted entirely (https://stackoverflow.com/a/1188145)
domain: `.${LEARNHOUSE_TOP_DOMAIN}`,
secure: !isDevEnv,
},
},
},
callbacks: {
async jwt({ token, user, account }: any) {
// First sign in with Credentials provider
if (account?.provider == 'credentials' && user) {
token.user = user
}
// Sign up with Google
if (account?.provider == 'google' && user) {
let unsanitized_req = await loginWithOAuthToken(
user.email,
'google',
account.access_token
)
let userFromOAuth = await getResponseMetadata(unsanitized_req)
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,
},
},
}
}
return token
},
async session({ session, token }: any) {
// Include user information in the session
if (token.user) {
let api_SESSION = await getUserSession(token.user.tokens.access_token)
session.user = api_SESSION.user
session.roles = api_SESSION.roles
session.tokens = token.user.tokens
}
return session
},
},
}

View file

@ -15,7 +15,7 @@ import { getUriWithOrg } from '@services/config/config'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import { resetPassword, sendResetLink } from '@services/auth/auth' import { resetPassword } from '@services/auth/auth'
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}

View file

@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { signUpWithInviteCode } from '@services/auth/auth' import { signUpWithInviteCode } from '@services/auth/auth'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { signIn } from 'next-auth/react'
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}
@ -92,7 +93,7 @@ function InviteOnlySignUpComponent(props: InviteOnlySignUpProps) {
}, },
}) })
useEffect(() => {}, [org]) useEffect(() => { }, [org])
return ( return (
<div className="login-form m-auto w-72"> <div className="login-form m-auto w-72">
@ -180,6 +181,13 @@ function InviteOnlySignUpComponent(props: InviteOnlySignUpProps) {
</Form.Submit> </Form.Submit>
</div> </div>
</FormLayout> </FormLayout>
<div>
<div className='flex h-0.5 rounded-2xl bg-slate-100 mt-5 mb-5 mx-10'></div>
<button onClick={() => signIn('google')} className="flex justify-center py-3 text-md w-full bg-white text-slate-600 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<img src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" alt="" />
<span>Sign in with Google</span>
</button>
</div>
</div> </div>
) )
} }

View file

@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { signup } from '@services/auth/auth' import { signup } from '@services/auth/auth'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { signIn } from 'next-auth/react'
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}
@ -88,7 +89,7 @@ function OpenSignUpComponent() {
}, },
}) })
useEffect(() => {}, [org]) useEffect(() => { }, [org])
return ( return (
<div className="login-form m-auto w-72"> <div className="login-form m-auto w-72">
@ -176,6 +177,13 @@ function OpenSignUpComponent() {
</Form.Submit> </Form.Submit>
</div> </div>
</FormLayout> </FormLayout>
<div>
<div className='flex h-0.5 rounded-2xl bg-slate-100 mt-5 mb-5 mx-10'></div>
<button onClick={() => signIn('google')} className="flex justify-center py-3 text-md w-full bg-white text-slate-600 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<img src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" alt="" />
<span>Sign in with Google</span>
</button>
</div>
</div> </div>
) )
} }

View file

@ -9,10 +9,10 @@ type MetadataProps = {
searchParams: { [key: string]: string | string[] | undefined } searchParams: { [key: string]: string | string[] | undefined }
} }
export async function generateMetadata({ export async function generateMetadata(
params, params
}: MetadataProps): Promise<Metadata> { : MetadataProps): Promise<Metadata> {
const orgslug = params.orgslug const orgslug = params.searchParams.orgslug
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(orgslug, { const org = await getOrganizationContextInfo(orgslug, {
revalidate: 0, revalidate: 0,
@ -25,7 +25,7 @@ export async function generateMetadata({
} }
const SignUp = async (params: any) => { const SignUp = async (params: any) => {
const orgslug = params.params.orgslug const orgslug = params.searchParams.orgslug
const org = await getOrganizationContextInfo(orgslug, { const org = await getOrganizationContextInfo(orgslug, {
revalidate: 0, revalidate: 0,
tags: ['organizations'], tags: ['organizations'],

View file

@ -4,9 +4,9 @@ import Image from 'next/image'
import { getOrgLogoMediaDirectory } from '@services/media/media' import { getOrgLogoMediaDirectory } from '@services/media/media'
import Link from 'next/link' import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { MailWarning, Shield, Ticket, UserPlus } from 'lucide-react' import { MailWarning, Ticket, UserPlus } from 'lucide-react'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import UserAvatar from '@components/Objects/UserAvatar' import UserAvatar from '@components/Objects/UserAvatar'
import OpenSignUpComponent from './OpenSignup' import OpenSignUpComponent from './OpenSignup'
@ -16,13 +16,15 @@ import { validateInviteCode } from '@services/organizations/invites'
import PageLoading from '@components/Objects/Loaders/PageLoading' import PageLoading from '@components/Objects/Loaders/PageLoading'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { BarLoader } from 'react-spinners'
import { joinOrg } from '@services/organizations/orgs'
interface SignUpClientProps { interface SignUpClientProps {
org: any org: any
} }
function SignUpClient(props: SignUpClientProps) { function SignUpClient(props: SignUpClientProps) {
const session = useSession() as any const session = useLHSession() as any
const [joinMethod, setJoinMethod] = React.useState('open') const [joinMethod, setJoinMethod] = React.useState('open')
const [inviteCode, setInviteCode] = React.useState('') const [inviteCode, setInviteCode] = React.useState('')
const searchParams = useSearchParams() const searchParams = useSearchParams()
@ -33,9 +35,6 @@ function SignUpClient(props: SignUpClientProps) {
setJoinMethod( setJoinMethod(
props.org?.config?.config?.GeneralConfig.users.signup_mechanism props.org?.config?.config?.GeneralConfig.users.signup_mechanism
) )
console.log(
props.org?.config?.config?.GeneralConfig.users.signup_mechanism
)
} }
if (inviteCodeParam) { if (inviteCodeParam) {
setInviteCode(inviteCodeParam) setInviteCode(inviteCodeParam)
@ -72,7 +71,7 @@ function SignUpClient(props: SignUpClientProps) {
props.org.org_uuid, props.org.org_uuid,
props.org?.logo_image props.org?.logo_image
)}`} )}`}
alt="Learnhouse" alt="LearnHouse"
style={{ width: 'auto', height: 70 }} style={{ width: 'auto', height: 70 }}
className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white" className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
/> />
@ -92,15 +91,15 @@ function SignUpClient(props: SignUpClientProps) {
</div> </div>
<div className="left-join-part bg-white flex flex-row"> <div className="left-join-part bg-white flex flex-row">
{joinMethod == 'open' && {joinMethod == 'open' &&
(session.isAuthenticated ? ( (session.status == 'authenticated' ? (
<LoggedInJoinScreen inviteCode={inviteCode} /> <LoggedInJoinScreen inviteCode={inviteCode} />
) : ( ) : (
<OpenSignUpComponent /> <OpenSignUpComponent />
))} ))}
{joinMethod == 'inviteOnly' && {joinMethod == 'inviteOnly' &&
(inviteCode ? ( (inviteCode ? (
session.isAuthenticated ? ( session.status == 'authenticated' ? (
<LoggedInJoinScreen /> <LoggedInJoinScreen inviteCode={inviteCode} />
) : ( ) : (
<InviteOnlySignUpComponent inviteCode={inviteCode} /> <InviteOnlySignUpComponent inviteCode={inviteCode} />
) )
@ -113,9 +112,32 @@ function SignUpClient(props: SignUpClientProps) {
} }
const LoggedInJoinScreen = (props: any) => { const LoggedInJoinScreen = (props: any) => {
const session = useSession() as any const session = useLHSession() as any
const org = useOrg() as any const org = useOrg() as any
const invite_code = props.inviteCode
const [isLoading, setIsLoading] = React.useState(true) const [isLoading, setIsLoading] = React.useState(true)
const [isSumbitting, setIsSubmitting] = React.useState(false)
const router = useRouter()
const join = async () => {
setIsSubmitting(true)
const res = await joinOrg({ org_id: org.id, user_id: session?.data?.user?.id, invite_code: props.inviteCode }, null, session.data?.tokens?.access_token)
//wait for 1s
if (res.success) {
toast.success(
res.data
)
setTimeout(() => {
router.push(getUriWithOrg(org.slug,'/'))
}, 2000)
setIsSubmitting(false)
} else {
toast.error(res.data.detail)
setIsLoading(false)
setIsSubmitting(false)
}
}
useEffect(() => { useEffect(() => {
if (session && org) { if (session && org) {
@ -125,18 +147,23 @@ const LoggedInJoinScreen = (props: any) => {
return ( return (
<div className="flex flex-row items-center mx-auto"> <div className="flex flex-row items-center mx-auto">
<Toast />
<div className="flex space-y-7 flex-col justify-center items-center"> <div className="flex space-y-7 flex-col justify-center items-center">
<p className="pt-3 text-2xl font-semibold text-black/70 flex justify-center space-x-2 items-center"> <p className="pt-3 text-2xl font-semibold text-black/70 flex justify-center space-x-2 items-center">
<span className="items-center">Hi</span> <span className="items-center">Hi</span>
<span className="capitalize flex space-x-2 items-center"> <span className="capitalize flex space-x-2 items-center">
<UserAvatar rounded="rounded-xl" border="border-4" width={35} /> <UserAvatar rounded="rounded-xl" border="border-4" width={35} />
<span>{session.user.username},</span> <span>{session.data.username},</span>
</span> </span>
<span>join {org?.name} ?</span> <span>join {org?.name} ?</span>
</p> </p>
<button className="flex w-fit space-x-2 bg-black px-6 py-2 text-md rounded-lg font-semibold h-fit text-white items-center shadow-md"> <button onClick={() => join()} className="flex w-fit h-[35px] space-x-2 bg-black px-6 py-2 text-md rounded-lg font-semibold h-fit text-white items-center shadow-md">
<UserPlus size={18} /> {isSumbitting ? <BarLoader
<p>Join </p> cssOverride={{ borderRadius: 60 }}
width={60}
color="#ffffff"
/> : <><UserPlus size={18} />
<p>Join </p></>}
</button> </button>
</div> </div>
</div> </div>
@ -144,7 +171,7 @@ const LoggedInJoinScreen = (props: any) => {
} }
const NoTokenScreen = (props: any) => { const NoTokenScreen = (props: any) => {
const session = useSession() as any const session = useLHSession() as any
const org = useOrg() as any const org = useOrg() as any
const router = useRouter() const router = useRouter()
const [isLoading, setIsLoading] = React.useState(true) const [isLoading, setIsLoading] = React.useState(true)
@ -157,14 +184,14 @@ const NoTokenScreen = (props: any) => {
const validateCode = async () => { const validateCode = async () => {
setIsLoading(true) setIsLoading(true)
let res = await validateInviteCode(org?.id, inviteCode) let res = await validateInviteCode(org?.id, inviteCode, session?.user?.tokens.access_token)
//wait for 1s //wait for 1s
if (res.success) { if (res.success) {
toast.success( toast.success(
"Invite code is valid, you'll be redirected to the signup page in a few seconds" "Invite code is valid, you'll be redirected to the signup page in a few seconds"
) )
setTimeout(() => { setTimeout(() => {
router.push(`/signup?inviteCode=${inviteCode}`) router.push(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`)
}, 2000) }, 2000)
} else { } else {
toast.error('Invite code is invalid') toast.error('Invite code is invalid')

View file

@ -1,14 +1,13 @@
import { default as React } from 'react' import { default as React } from 'react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses' import { getCourseMetadata } from '@services/courses/courses'
import { cookies } from 'next/headers'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getActivityWithAuthHeader } from '@services/courses/activities' import { getActivityWithAuthHeader } from '@services/courses/activities'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import { getOrganizationContextInfoWithId } from '@services/organizations/orgs' import { getOrganizationContextInfoWithId } from '@services/organizations/orgs'
import SessionProvider from '@components/Contexts/SessionContext'
import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext' import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext'
import AIEditorProvider from '@components/Contexts/AI/AIEditorContext' import AIEditorProvider from '@components/Contexts/AI/AIEditorContext'
import { nextAuthOptions } from 'app/auth/options'
import { getServerSession } from 'next-auth'
const EditorWrapper = dynamic(() => import('@components/Objects/Editor/EditorWrapper'), { ssr: false }) const EditorWrapper = dynamic(() => import('@components/Objects/Editor/EditorWrapper'), { ssr: false })
@ -20,10 +19,10 @@ type MetadataProps = {
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: MetadataProps): Promise<Metadata> { }: MetadataProps): Promise<Metadata> {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
// Get Org context information // Get Org context information
const course_meta = await getCourseMetadataWithAuthHeader( const course_meta = await getCourseMetadata(
params.courseid, params.courseid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null
@ -36,11 +35,11 @@ export async function generateMetadata({
} }
const EditActivity = async (params: any) => { const EditActivity = async (params: any) => {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const activityuuid = params.params.activityuuid const activityuuid = params.params.activityuuid
const courseid = params.params.courseid const courseid = params.params.courseid
const courseInfo = await getCourseMetadataWithAuthHeader( const courseInfo = await getCourseMetadata(
courseid, courseid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null
@ -53,19 +52,17 @@ const EditActivity = async (params: any) => {
const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { const org = await getOrganizationContextInfoWithId(courseInfo.org_id, {
revalidate: 180, revalidate: 180,
tags: ['organizations'], tags: ['organizations'],
}) }, access_token)
return ( return (
<EditorOptionsProvider options={{ isEditable: true }}> <EditorOptionsProvider options={{ isEditable: true }}>
<AIEditorProvider> <AIEditorProvider>
<SessionProvider>
<EditorWrapper <EditorWrapper
org={org} org={org}
course={courseInfo} course={courseInfo}
activity={activity} activity={activity}
content={activity.content} content={activity.content}
></EditorWrapper> ></EditorWrapper>
</SessionProvider>
</AIEditorProvider> </AIEditorProvider>
</EditorOptionsProvider> </EditorOptionsProvider>
) )

View file

@ -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 (
<div className='flex flex-col'>
<div className='flex space-x-4 mx-auto font-semibold text-3xl pt-16 items-center bg-black rounded-b-2xl'>
<Image
quality={100}
width={60}
height={60}
src={learnhouseIcon}
alt=""
/>
</div>
<div className='flex space-x-4 mx-auto font-semibold text-2xl pt-16 items-center'><span>Hello,</span> <UserAvatar /> <span className='capitalize'>{session?.data?.user.first_name} {session?.data?.user.last_name}</span></div>
<div className='flex space-x-4 mx-auto font-semibold text-sm mt-12 items-center uppercase bg-slate-200 text-gray-600 px-3 py-2 rounded-md'>Your Organizations</div>
{orgs && orgs.length == 0 && <div className='flex mx-auto my-5 space-x-3 bg-rose-200 rounded-lg px-3 py-2'>
<Info />
<span>It seems you're not part of an organization yet, join one to be able to see it here </span>
</div>}
<div className='flex mx-auto pt-10 rounded-lg'>
{orgs && orgs.map((org: any) => (
<Link href={getUriWithOrg(org.slug, '/')} key={org.id} className='flex space-x-2 mx-auto w-fit justify-between items-center outline outline-1 outline-slate-200 px-3 py-2 rounded-lg'>
<div>{org.name}</div>
<ArrowRightCircle />
</Link>
))}
</div>
<div className='flex cursor-pointer space-x-4 mx-auto font-semibold text-2xl pt-16 items-center'><span onClick={() => signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/') })}>Sign out</span></div>
</div>
)
}
export default HomeClient

View file

@ -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 (
<div>
<HomeClient/>
</div>
)
}
export default Home

View file

@ -10,6 +10,7 @@ import { getAPIUrl } from '@services/config/config'
import { createNewUserInstall, updateInstall } from '@services/install/install' import { createNewUserInstall, updateInstall } from '@services/install/install'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
import { BarLoader } from 'react-spinners' import { BarLoader } from 'react-spinners'
@ -47,11 +48,13 @@ const validate = (values: any) => {
function AccountCreation() { function AccountCreation() {
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const router = useRouter() const router = useRouter()
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {

View file

@ -1,16 +1,19 @@
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { createDefaultElements, updateInstall } from '@services/install/install' import { createDefaultElements, updateInstall } from '@services/install/install'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
import useSWR from 'swr' import useSWR from 'swr'
function DefaultElements() { function DefaultElements() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
const [isSubmitted, setIsSubmitted] = React.useState(false) const [isSubmitted, setIsSubmitted] = React.useState(false)
const router = useRouter() const router = useRouter()

View file

@ -2,16 +2,19 @@ import { getAPIUrl } from '@services/config/config'
import { updateInstall } from '@services/install/install' import { updateInstall } from '@services/install/install'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Check } from 'lucide-react' import { Check } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
import useSWR from 'swr' import useSWR from 'swr'
const Finish = () => { const Finish = () => {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const router = useRouter() const router = useRouter()
async function finishInstall() { async function finishInstall() {

View file

@ -1,16 +1,19 @@
import PageLoading from '@components/Objects/Loaders/PageLoading' import PageLoading from '@components/Objects/Loaders/PageLoading'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
function GetStarted() { function GetStarted() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const router = useRouter() const router = useRouter()
async function startInstallation() { async function startInstallation() {

View file

@ -14,6 +14,7 @@ import useSWR from 'swr'
import { createNewOrgInstall, updateInstall } from '@services/install/install' import { createNewOrgInstall, updateInstall } from '@services/install/install'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { Check } from 'lucide-react' import { Check } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}
@ -40,11 +41,13 @@ const validate = (values: any) => {
} }
function OrgCreation() { function OrgCreation() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
const [isSubmitted, setIsSubmitted] = React.useState(false) const [isSubmitted, setIsSubmitted] = React.useState(false)
const router = useRouter() const router = useRouter()

View file

@ -4,16 +4,19 @@ import {
updateInstall, updateInstall,
} from '@services/install/install' } from '@services/install/install'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
import useSWR from 'swr' import useSWR from 'swr'
function SampleData() { function SampleData() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { const {
data: install, data: install,
error: error, error: error,
isLoading, isLoading,
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher) } = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
const router = useRouter() const router = useRouter()
function createSampleData() { function createSampleData() {

View file

@ -1,8 +1,9 @@
'use client' 'use client'
import '../styles/globals.css' import '../styles/globals.css'
import StyledComponentsRegistry from '../components/Utils/libs/styled-registry' import StyledComponentsRegistry from '../components/Utils/libs/styled-registry'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { SessionProvider } from 'next-auth/react'
import LHSessionProvider from '@components/Contexts/LHSessionContext'
export default function RootLayout({ export default function RootLayout({
children, children,
@ -18,6 +19,8 @@ export default function RootLayout({
<html className="" lang="en"> <html className="" lang="en">
<head /> <head />
<body> <body>
<SessionProvider>
<LHSessionProvider>
<StyledComponentsRegistry> <StyledComponentsRegistry>
<motion.main <motion.main
variants={variants} // Pass the variant object into Framer Motion variants={variants} // Pass the variant object into Framer Motion
@ -30,6 +33,8 @@ export default function RootLayout({
{children} {children}
</motion.main> </motion.main>
</StyledComponentsRegistry> </StyledComponentsRegistry>
</LHSessionProvider>
</SessionProvider>
</body> </body>
</html> </html>
) )

View file

@ -1,58 +0,0 @@
'use client'
import React from 'react'
import { createNewOrganization } from '../../../services/organizations/orgs'
const Organizations = () => {
const [name, setName] = React.useState('')
const [description, setDescription] = React.useState('')
const [email, setEmail] = React.useState('')
const [slug, setSlug] = React.useState('')
const handleNameChange = (e: any) => {
setName(e.target.value)
}
const handleDescriptionChange = (e: any) => {
setDescription(e.target.value)
}
const handleEmailChange = (e: any) => {
setEmail(e.target.value)
}
const handleSlugChange = (e: any) => {
setSlug(e.target.value)
}
const handleSubmit = async (e: any) => {
e.preventDefault()
let logo = ''
const status = await createNewOrganization({
name,
description,
email,
logo,
slug,
default: false,
})
alert(JSON.stringify(status))
}
return (
<div>
<div className="font-bold text-lg">New Organization</div>
Name: <input onChange={handleNameChange} type="text" />
<br />
Description: <input onChange={handleDescriptionChange} type="text" />
<br />
Slug: <input onChange={handleSlugChange} type="text" />
<br />
Email Address: <input onChange={handleEmailChange} type="text" />
<br />
<button onClick={handleSubmit}>Create</button>
</div>
)
}
export default Organizations

View file

@ -1,65 +0,0 @@
'use client' //todo: use server components
import Link from 'next/link'
import React from 'react'
import { deleteOrganizationFromBackend } from '@services/organizations/orgs'
import useSWR, { mutate } from 'swr'
import { swrFetcher } from '@services/utils/ts/requests'
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
const Organizations = () => {
const { data: organizations, error } = useSWR(
`${getAPIUrl()}orgs/user/page/1/limit/10`,
swrFetcher
)
async function deleteOrganization(org_id: any) {
const response = await deleteOrganizationFromBackend(org_id)
response &&
mutate(
`${getAPIUrl()}orgs/user/page/1/limit/10`,
organizations.filter((org: any) => org.org_id !== org_id)
)
}
return (
<>
<div className="font-bold text-lg">
Your Organizations{' '}
<Link href="/organizations/new">
<button className="bg-blue-500 text-white px-2 py-1 rounded-md hover:bg-blue-600 focus:outline-none">
+
</button>
</Link>
</div>
<hr />
{error && <p className="text-red-500">Failed to load</p>}
{!organizations ? (
<p className="text-gray-500">Loading...</p>
) : (
<div>
{organizations.map((org: any) => (
<div
key={org.org_id}
className="flex items-center justify-between mb-4"
>
<Link href={getUriWithOrg(org.slug, '/')}>
<h3 className="text-blue-500 cursor-pointer hover:underline">
{org.name}
</h3>
</Link>
<button
onClick={() => deleteOrganization(org.org_id)}
className="px-3 py-1 text-white bg-red-500 rounded-md hover:bg-red-600 focus:outline-none"
>
Delete
</button>
</div>
))}
</div>
)}
</>
)
}
export default Organizations

View file

@ -1,11 +1,11 @@
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper' import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { getCollectionByIdWithAuthHeader } from '@services/courses/collections' import { getCollectionById } from '@services/courses/collections'
import { getCourseThumbnailMediaDirectory } from '@services/media/media' import { getCourseThumbnailMediaDirectory } from '@services/media/media'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { nextAuthOptions } from 'app/auth/options'
import { Metadata } from 'next' import { Metadata } from 'next'
import { cookies } from 'next/headers' import { getServerSession } from 'next-auth'
import Link from 'next/link' import Link from 'next/link'
type MetadataProps = { type MetadataProps = {
@ -16,15 +16,15 @@ type MetadataProps = {
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: MetadataProps): Promise<Metadata> { }: MetadataProps): Promise<Metadata> {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { const org = await getOrganizationContextInfo(params.orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const col = await getCollectionByIdWithAuthHeader( const col = await getCollectionById(
params.collectionid, params.collectionid,
access_token ? access_token : null, access_token ? access_token : null,
{ revalidate: 0, tags: ['collections'] } { revalidate: 0, tags: ['collections'] }
@ -53,14 +53,14 @@ export async function generateMetadata({
} }
const CollectionPage = async (params: any) => { const CollectionPage = async (params: any) => {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const org = await getOrganizationContextInfo(params.params.orgslug, { const org = await getOrganizationContextInfo(params.params.orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const col = await getCollectionByIdWithAuthHeader( const col = await getCollectionById(
params.params.collectionid, params.params.collectionid,
access_token ? access_token : null, access_token ? access_token : null,
{ revalidate: 0, tags: ['collections'] } { revalidate: 0, tags: ['collections'] }

View file

@ -6,9 +6,12 @@ import useSWR from 'swr'
import { getAPIUrl, getUriWithOrg } from '@services/config/config' import { getAPIUrl, getUriWithOrg } from '@services/config/config'
import { revalidateTags, swrFetcher } from '@services/utils/ts/requests' import { revalidateTags, swrFetcher } from '@services/utils/ts/requests'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function NewCollection(params: any) { function NewCollection(params: any) {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const [name, setName] = React.useState('') const [name, setName] = React.useState('')
const [description, setDescription] = React.useState('') const [description, setDescription] = React.useState('')
@ -16,7 +19,7 @@ function NewCollection(params: any) {
const router = useRouter() const router = useRouter()
const { data: courses, error: error } = useSWR( const { data: courses, error: error } = useSWR(
`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`, `${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const [isPublic, setIsPublic] = useState('true') const [isPublic, setIsPublic] = useState('true')
@ -44,7 +47,7 @@ function NewCollection(params: any) {
public: isPublic, public: isPublic,
org_id: org.id, org_id: org.id,
} }
await createCollection(collection) await createCollection(collection, session.data?.tokens?.access_token)
await revalidateTags(['collections'], org.slug) await revalidateTags(['collections'], org.slug)
// reload the page // reload the page
router.refresh() router.refresh()

View file

@ -2,15 +2,15 @@ import AuthenticatedClientElement from '@components/Security/AuthenticatedClient
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle' import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper' import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { getOrgCollectionsWithAuthHeader } from '@services/courses/collections'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { Metadata } from 'next' import { Metadata } from 'next'
import { cookies } from 'next/headers'
import Link from 'next/link' import Link from 'next/link'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail' import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'
import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton' import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton'
import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder' import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
import { nextAuthOptions } from 'app/auth/options'
import { getServerSession } from 'next-auth'
import { getOrgCollections } from '@services/courses/collections'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string; courseid: string } params: { orgslug: string; courseid: string }
@ -49,15 +49,15 @@ export async function generateMetadata({
} }
const CollectionsPage = async (params: any) => { const CollectionsPage = async (params: any) => {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const org = await getOrganizationContextInfo(orgslug, { const org = await getOrganizationContextInfo(orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const org_id = org.id const org_id = org.id
const collections = await getOrgCollectionsWithAuthHeader( const collections = await getOrgCollections(
org_id, org_id,
access_token ? access_token : null, access_token ? access_token : null,
{ revalidate: 0, tags: ['collections'] } { revalidate: 0, tags: ['collections'] }

View file

@ -15,6 +15,7 @@ import { useOrg } from '@components/Contexts/OrgContext'
import { CourseProvider } from '@components/Contexts/CourseContext' import { CourseProvider } from '@components/Contexts/CourseContext'
import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk' import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk'
import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext' import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
interface ActivityClientProps { interface ActivityClientProps {
activityid: string activityid: string
@ -106,8 +107,7 @@ function ActivityClient(props: ActivityClientProps) {
{activity ? ( {activity ? (
<div <div
className={`p-7 pt-4 drop-shadow-sm rounded-lg ${ className={`p-7 pt-4 drop-shadow-sm rounded-lg ${activity.activity_type == 'TYPE_DYNAMIC'
activity.activity_type == 'TYPE_DYNAMIC'
? 'bg-white' ? 'bg-white'
: 'bg-zinc-950' : 'bg-zinc-950'
}`} }`}
@ -147,13 +147,14 @@ export function MarkStatus(props: {
orgslug: string orgslug: string
}) { }) {
const router = useRouter() const router = useRouter()
console.log(props.course.trail) const session = useLHSession() as any;
async function markActivityAsCompleteFront() { async function markActivityAsCompleteFront() {
const trail = await markActivityAsComplete( const trail = await markActivityAsComplete(
props.orgslug, props.orgslug,
props.course.course_uuid, props.course.course_uuid,
'activity_' + props.activityid 'activity_' + props.activityid,
session.data?.tokens?.access_token
) )
router.refresh() router.refresh()
} }
@ -169,8 +170,6 @@ export function MarkStatus(props: {
} }
} }
console.log('isActivityCompleted', isActivityCompleted())
return ( return (
<> <>
{isActivityCompleted() ? ( {isActivityCompleted() ? (

View file

@ -1,10 +1,10 @@
import { getActivityWithAuthHeader } from '@services/courses/activities' import { getActivityWithAuthHeader } from '@services/courses/activities'
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses' import { getCourseMetadata } from '@services/courses/courses'
import { cookies } from 'next/headers'
import ActivityClient from './activity' import ActivityClient from './activity'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth' import { getServerSession } from 'next-auth'
import { nextAuthOptions } from 'app/auth/options'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string; courseuuid: string; activityid: string } params: { orgslug: string; courseuuid: string; activityid: string }
@ -14,15 +14,15 @@ type MetadataProps = {
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: MetadataProps): Promise<Metadata> { }: MetadataProps): Promise<Metadata> {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { const org = await getOrganizationContextInfo(params.orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const course_meta = await getCourseMetadataWithAuthHeader( const course_meta = await getCourseMetadata(
params.courseuuid, params.courseuuid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null
@ -58,13 +58,13 @@ export async function generateMetadata({
} }
const ActivityPage = async (params: any) => { const ActivityPage = async (params: any) => {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const activityid = params.params.activityid const activityid = params.params.activityid
const courseuuid = params.params.courseuuid const courseuuid = params.params.courseuuid
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const course_meta = await getCourseMetadataWithAuthHeader( const course_meta = await getCourseMetadata(
courseuuid, courseuuid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null

View file

@ -17,10 +17,12 @@ import { useOrg } from '@components/Contexts/OrgContext'
import UserAvatar from '@components/Objects/UserAvatar' import UserAvatar from '@components/Objects/UserAvatar'
import CourseUpdates from '@components/Objects/CourseUpdates/CourseUpdates' import CourseUpdates from '@components/Objects/CourseUpdates/CourseUpdates'
import { CourseProvider } from '@components/Contexts/CourseContext' import { CourseProvider } from '@components/Contexts/CourseContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
const CourseClient = (props: any) => { const CourseClient = (props: any) => {
const [user, setUser] = useState<any>({}) const [user, setUser] = useState<any>({})
const [learnings, setLearnings] = useState<any>([]) const [learnings, setLearnings] = useState<any>([])
const session = useLHSession() as any;
const courseuuid = props.courseuuid const courseuuid = props.courseuuid
const orgslug = props.orgslug const orgslug = props.orgslug
const course = props.course const course = props.course
@ -35,7 +37,7 @@ const CourseClient = (props: any) => {
async function startCourseUI() { async function startCourseUI() {
// Create activity // Create activity
await startCourse('course_' + courseuuid, orgslug) await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
await revalidateTags(['courses'], orgslug) await revalidateTags(['courses'], orgslug)
router.refresh() router.refresh()
@ -54,7 +56,7 @@ const CourseClient = (props: any) => {
async function quitCourse() { async function quitCourse() {
// Close activity // Close activity
let activity = await removeCourse('course_' + courseuuid, orgslug) let activity = await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
// Mutate course // Mutate course
await revalidateTags(['courses'], orgslug) await revalidateTags(['courses'], orgslug)
router.refresh() router.refresh()

View file

@ -1,6 +1,5 @@
'use client' // Error components must be Client Components 'use client' // Error components must be Client Components
import ErrorUI from '@components/StyledElements/Error/Error'
import { useEffect } from 'react' import { useEffect } from 'react'
export default function Error({ export default function Error({
@ -17,7 +16,7 @@ export default function Error({
return ( return (
<div> <div>
<ErrorUI></ErrorUI>
</div> </div>
) )
} }

View file

@ -1,6 +0,0 @@
import PageLoading from '@components/Objects/Loaders/PageLoading'
export default function Loading() {
// Or a custom loading skeleton component
return <PageLoading></PageLoading>
}

View file

@ -1,11 +1,11 @@
import React from 'react' import React from 'react'
import CourseClient from './course' import CourseClient from './course'
import { cookies } from 'next/headers' import { getCourseMetadata } from '@services/courses/courses'
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import { getCourseThumbnailMediaDirectory } from '@services/media/media' import { getCourseThumbnailMediaDirectory } from '@services/media/media'
import { nextAuthOptions } from 'app/auth/options'
import { getServerSession } from 'next-auth'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string; courseuuid: string } params: { orgslug: string; courseuuid: string }
@ -15,15 +15,15 @@ type MetadataProps = {
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: MetadataProps): Promise<Metadata> { }: MetadataProps): Promise<Metadata> {
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { const org = await getOrganizationContextInfo(params.orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const course_meta = await getCourseMetadataWithAuthHeader( const course_meta = await getCourseMetadata(
params.courseuuid, params.courseuuid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null
@ -67,11 +67,11 @@ export async function generateMetadata({
} }
const CoursePage = async (params: any) => { const CoursePage = async (params: any) => {
const cookieStore = cookies()
const courseuuid = params.params.courseuuid const courseuuid = params.params.courseuuid
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const session = await getServerSession(nextAuthOptions)
const course_meta = await getCourseMetadataWithAuthHeader( const access_token = session?.tokens?.access_token
const course_meta = await getCourseMetadata(
courseuuid, courseuuid,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null

View file

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
import Courses from './courses' import Courses from './courses'
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { cookies } from 'next/headers' import { nextAuthOptions } from 'app/auth/options'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth' import { getServerSession } from 'next-auth'
import { getOrgCourses } from '@services/courses/courses'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string } params: { orgslug: string }
@ -49,9 +49,10 @@ const CoursesPage = async (params: any) => {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const courses = await getOrgCoursesWithAuthHeader(
const courses = await getOrgCourses(
orgslug, orgslug,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null

View file

@ -1,6 +1,7 @@
'use client'
import '@styles/globals.css' import '@styles/globals.css'
import { Menu } from '@components/Objects/Menu/Menu' import { Menu } from '@components/Objects/Menu/Menu'
import SessionProvider from '@components/Contexts/SessionContext' import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ export default function RootLayout({
children, children,

View file

@ -1,20 +1,20 @@
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses' import { getOrgCourses } from '@services/courses/courses'
import Link from 'next/link' import Link from 'next/link'
import { getOrgCollectionsWithAuthHeader } from '@services/courses/collections'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { cookies } from 'next/headers'
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper' import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle' import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail' import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail'
import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail' import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement' import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton' import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'
import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton' import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton'
import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder' import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
import { getOrgCollections } from '@services/courses/collections'
import { getServerSession } from 'next-auth'
import { nextAuthOptions } from 'app/auth/options'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string } params: { orgslug: string }
@ -54,10 +54,9 @@ export async function generateMetadata({
const OrgHomePage = async (params: any) => { const OrgHomePage = async (params: any) => {
const orgslug = params.params.orgslug const orgslug = params.params.orgslug
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = session?.tokens?.access_token
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const courses = await getOrgCourses(
const courses = await getOrgCoursesWithAuthHeader(
orgslug, orgslug,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null
@ -67,7 +66,7 @@ const OrgHomePage = async (params: any) => {
tags: ['organizations'], tags: ['organizations'],
}) })
const org_id = org.id const org_id = org.id
const collections = await getOrgCollectionsWithAuthHeader( const collections = await getOrgCollections(
org.id, org.id,
access_token ? access_token : null, access_token ? access_token : null,
{ revalidate: 0, tags: ['courses'] } { revalidate: 0, tags: ['courses'] }

View file

@ -2,6 +2,8 @@ import React from 'react'
import { Metadata } from 'next' import { Metadata } from 'next'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import Trail from './trail' import Trail from './trail'
import { getServerSession } from 'next-auth'
import { nextAuthOptions } from 'app/auth/options'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string } params: { orgslug: string }
@ -11,11 +13,13 @@ type MetadataProps = {
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: MetadataProps): Promise<Metadata> { }: MetadataProps): Promise<Metadata> {
const session = await getServerSession(nextAuthOptions)
const access_token = session?.tokens?.access_token
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { const org = await getOrganizationContextInfo(params.orgslug, {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) }, access_token)
return { return {
title: 'Trail — ' + org.name, title: 'Trail — ' + org.name,
description: description:

View file

@ -1,4 +1,5 @@
'use client' 'use client'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import PageLoading from '@components/Objects/Loaders/PageLoading' import PageLoading from '@components/Objects/Loaders/PageLoading'
import TrailCourseElement from '@components/Pages/Trail/TrailCourseElement' import TrailCourseElement from '@components/Pages/Trail/TrailCourseElement'
@ -11,14 +12,16 @@ import useSWR from 'swr'
function Trail(params: any) { function Trail(params: any) {
let orgslug = params.orgslug let orgslug = params.orgslug
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const org = useOrg() as any const org = useOrg() as any
const orgID = org?.id const orgID = org?.id
const { data: trail, error: error } = useSWR( const { data: trail, error: error } = useSWR(
`${getAPIUrl()}trail/org/${orgID}/trail`, `${getAPIUrl()}trail/org/${orgID}/trail`,
swrFetcher (url) => swrFetcher(url, access_token)
) )
useEffect(() => {}, [trail, org]) useEffect(() => { }, [trail, org])
return ( return (
<GeneralWrapperStyled> <GeneralWrapperStyled>

View file

@ -0,0 +1,26 @@
'use client';
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AdminAuthorization from '@components/Security/AdminAuthorization'
import { SessionProvider } from 'next-auth/react'
import React from 'react'
function ClientAdminLayout({
children,
params,
}: {
children: React.ReactNode
params: any
}) {
return (
<SessionProvider>
<AdminAuthorization authorizationMode="page">
<div className="flex">
<LeftMenu />
<div className="flex w-full">{children}</div>
</div>
</AdminAuthorization>
</SessionProvider>
)
}
export default ClientAdminLayout

View file

@ -30,7 +30,6 @@ function CoursesHome(params: CourseProps) {
<div> <div>
<div className="pl-10 mr-10 tracking-tighter"> <div className="pl-10 mr-10 tracking-tighter">
<BreadCrumbs type="courses" /> <BreadCrumbs type="courses" />
<div className="w-100 flex justify-between"> <div className="w-100 flex justify-between">
<div className="pt-3 flex font-bold text-4xl">Courses</div> <div className="pt-3 flex font-bold text-4xl">Courses</div>
<AuthenticatedClientElement <AuthenticatedClientElement

View file

@ -7,7 +7,7 @@ import Link from 'next/link'
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop' import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import EditCourseGeneral from '@components/Dashboard/Course/EditCourseGeneral/EditCourseGeneral' import EditCourseGeneral from '@components/Dashboard/Course/EditCourseGeneral/EditCourseGeneral'
import { GalleryVerticalEnd, Info, Lock, UserRoundCog } from 'lucide-react' import { GalleryVerticalEnd, Info, UserRoundCog } from 'lucide-react'
import EditCourseAccess from '@components/Dashboard/Course/EditCourseAccess/EditCourseAccess' import EditCourseAccess from '@components/Dashboard/Course/EditCourseAccess/EditCourseAccess'
export type CourseOverviewParams = { export type CourseOverviewParams = {

View file

@ -1,10 +1,10 @@
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses'
import { getOrganizationContextInfo } from '@services/organizations/orgs' import { getOrganizationContextInfo } from '@services/organizations/orgs'
import { Metadata } from 'next' import { Metadata } from 'next'
import { cookies } from 'next/headers'
import React from 'react' import React from 'react'
import CoursesHome from './client' import CoursesHome from './client'
import { nextAuthOptions } from 'app/auth/options'
import { getServerSession } from 'next-auth'
import { getOrgCourses } from '@services/courses/courses'
type MetadataProps = { type MetadataProps = {
params: { orgslug: string } params: { orgslug: string }
@ -49,9 +49,9 @@ async function CoursesPage(params: any) {
revalidate: 1800, revalidate: 1800,
tags: ['organizations'], tags: ['organizations'],
}) })
const cookieStore = cookies() const session = await getServerSession(nextAuthOptions)
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore) const access_token = session?.tokens?.access_token
const courses = await getOrgCoursesWithAuthHeader( const courses = await getOrgCourses(
orgslug, orgslug,
{ revalidate: 0, tags: ['courses'] }, { revalidate: 0, tags: ['courses'] },
access_token ? access_token : null access_token ? access_token : null

View file

@ -1,8 +1,6 @@
import SessionProvider from '@components/Contexts/SessionContext'
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AdminAuthorization from '@components/Security/AdminAuthorization'
import { Metadata } from 'next' import { Metadata } from 'next'
import React from 'react' import React from 'react'
import ClientAdminLayout from './ClientAdminLayout'
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'LearnHouse Dashboard', title: 'LearnHouse Dashboard',
@ -17,14 +15,10 @@ function DashboardLayout({
}) { }) {
return ( return (
<> <>
<SessionProvider> <ClientAdminLayout
<AdminAuthorization authorizationMode="page"> params={params}>
<div className="flex"> {children}
<LeftMenu /> </ClientAdminLayout>
<div className="flex w-full">{children}</div>
</div>
</AdminAuthorization>
</SessionProvider>
</> </>
) )
} }

View file

@ -7,7 +7,7 @@ import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { Info, Lock } from 'lucide-react' import { Info, Lock } from 'lucide-react'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
export type SettingsParams = { export type SettingsParams = {
subpage: string subpage: string
@ -15,7 +15,7 @@ export type SettingsParams = {
} }
function SettingsPage({ params }: { params: SettingsParams }) { function SettingsPage({ params }: { params: SettingsParams }) {
const session = useSession() as any const session = useLHSession() as any
useEffect(() => {}, [session]) useEffect(() => {}, [session])

View file

@ -5,7 +5,7 @@ import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react' import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers' import OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers'
import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess' import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess'
@ -18,7 +18,7 @@ export type SettingsParams = {
} }
function UsersSettingsPage({ params }: { params: SettingsParams }) { function UsersSettingsPage({ params }: { params: SettingsParams }) {
const session = useSession() as any const session = useLHSession() as any
const org = useOrg() as any const org = useOrg() as any
const [H1Label, setH1Label] = React.useState('') const [H1Label, setH1Label] = React.useState('')
const [H2Label, setH2Label] = React.useState('') const [H2Label, setH2Label] = React.useState('')

View file

@ -1,6 +1,5 @@
'use client' 'use client'
import { OrgProvider } from '@components/Contexts/OrgContext' import { OrgProvider } from '@components/Contexts/OrgContext'
import SessionProvider from '@components/Contexts/SessionContext'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
import '@styles/globals.css' import '@styles/globals.css'
@ -13,9 +12,9 @@ export default function RootLayout({
}) { }) {
return ( return (
<div> <div>
<Toast />
<OrgProvider orgslug={params.orgslug}> <OrgProvider orgslug={params.orgslug}>
<SessionProvider>{children}</SessionProvider> <Toast />
{children}
</OrgProvider> </OrgProvider>
</div> </div>
) )

View file

@ -13,17 +13,14 @@ function useGetAIFeatures(props: UseGetAIFeatures) {
const config = org?.config?.config?.AIConfig const config = org?.config?.config?.AIConfig
if (!config) { if (!config) {
console.log('AI or Organization config is not defined.')
return false return false
} }
if (!config.enabled) { if (!config.enabled) {
console.log('AI is not enabled for this Organization.')
return false return false
} }
if (!config.features[feature]) { if (!config.features[feature]) {
console.log(`Feature ${feature} is not enabled for this Organization.`)
return false return false
} }

View file

@ -7,7 +7,7 @@ import useAdminStatus from './Hooks/useAdminStatus'
function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) { function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) {
const isUserAdmin = useAdminStatus() as any const isUserAdmin = useAdminStatus() as any
return ( return (
<div>{isUserAdmin ? text : 'No content yet'}</div> <span>{isUserAdmin ? text : 'No content yet'}</span>
) )
} }

View file

@ -1,9 +1,9 @@
'use client' 'use client'
import PageLoading from '@components/Objects/Loaders/PageLoading'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import React, { createContext, useContext, useEffect, useReducer } from 'react' import React, { createContext, useContext, useEffect, useReducer } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useLHSession } from '@components/Contexts/LHSessionContext'
export const CourseContext = createContext(null) as any export const CourseContext = createContext(null) as any
export const CourseDispatchContext = createContext(null) as any export const CourseDispatchContext = createContext(null) as any
@ -15,9 +15,11 @@ export function CourseProvider({
children: React.ReactNode children: React.ReactNode
courseuuid: string courseuuid: string
}) { }) {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { data: courseStructureData } = useSWR( const { data: courseStructureData } = useSWR(
`${getAPIUrl()}courses/${courseuuid}/meta`, `${getAPIUrl()}courses/${courseuuid}/meta`,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const [courseStructure, dispatchCourseStructure] = useReducer(courseReducer, { const [courseStructure, dispatchCourseStructure] = useReducer(courseReducer, {
courseStructure: courseStructureData ? courseStructureData : {}, courseStructure: courseStructureData ? courseStructureData : {},
@ -33,9 +35,9 @@ export function CourseProvider({
payload: courseStructureData, payload: courseStructureData,
}) })
} }
}, [courseStructureData]) }, [courseStructureData,session])
if (!courseStructureData) return <PageLoading></PageLoading> if (!courseStructureData) return
return ( return (
<CourseContext.Provider value={courseStructure}> <CourseContext.Provider value={courseStructure}>

View file

@ -0,0 +1,32 @@
'use client'
import PageLoading from '@components/Objects/Loaders/PageLoading';
import { useSession } from 'next-auth/react';
import React, { useContext, createContext, useEffect } from 'react'
export const SessionContext = createContext({}) as any
function LHSessionProvider({ children }: { children: React.ReactNode }) {
const session = useSession();
useEffect(() => {
}, [])
if (session && session.status == 'loading') {
return <PageLoading />
}
else if (session) {
return (
<SessionContext.Provider value={session}>
{children}
</SessionContext.Provider>
)
}
}
export function useLHSession() {
return useContext(SessionContext)
}
export default LHSessionProvider

View file

@ -1,33 +1,47 @@
'use client' 'use client'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl, getUriWithoutOrg } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import React, { useContext, useEffect } from 'react' import React, { createContext, useContext, useMemo } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { createContext } from 'react' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' 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({ export function OrgProvider({ children, orgslug }: { children: React.ReactNode, orgslug: string }) {
children, const session = useLHSession() as any
orgslug, const pathname = usePathname()
}: { const accessToken = session?.data?.tokens?.access_token
children: React.ReactNode const isAllowedPathname = ['/login', '/signup'].includes(pathname);
orgslug: string
}) { const { data: org, error: orgError } = useSWR(
const { data: org } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher) `${getAPIUrl()}orgs/slug/${orgslug}`,
const router = useRouter() (url) => swrFetcher(url, accessToken)
// Check if Org is Active )
const verifyIfOrgIsActive = () => { const { data: orgs, error: orgsError } = useSWR(
if (org && org?.config.config.GeneralConfig.active === false) { `${getAPIUrl()}orgs/user/page/1/limit/10`,
router.push('/404') (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 <ErrorUI message='An error occurred while fetching data' />
if (!org || !orgs || !session) return <div>Loading...</div>
if (!isOrgActive) return <ErrorUI message='This organization is no longer active' />
if (!isUserPartOfTheOrg && session.status == 'authenticated' && !isAllowedPathname) {
return (
<InfoUI
href={getUriWithoutOrg(`/signup?orgslug=${orgslug}`)}
message='You are not part of this Organization yet'
cta={`Join ${org?.name}`}
/>
)
} }
}
useEffect(() => {
verifyIfOrgIsActive()
}, [org])
return <OrgContext.Provider value={org}>{children}</OrgContext.Provider> return <OrgContext.Provider value={org}>{children}</OrgContext.Provider>
} }

View file

@ -1,77 +0,0 @@
'use client'
import {
getNewAccessTokenUsingRefreshToken,
getUserSession,
} from '@services/auth/auth'
import React, { useContext, createContext, useEffect } from 'react'
export const SessionContext = createContext({}) as any
type Session = {
access_token: string
user: any
roles: any
isLoading: boolean
isAuthenticated: boolean
}
function SessionProvider({ children }: { children: React.ReactNode }) {
const [session, setSession] = React.useState<Session>({
access_token: '',
user: {},
roles: {},
isLoading: true,
isAuthenticated: false,
})
async function getNewAccessTokenUsingRefreshTokenUI() {
let data = await getNewAccessTokenUsingRefreshToken()
return data.access_token
}
async function checkSession() {
// Get new access token using refresh token
const access_token = await getNewAccessTokenUsingRefreshTokenUI()
if (access_token) {
// Get user session info
const user_session = await getUserSession(access_token)
// Set session
setSession({
access_token: access_token,
user: user_session.user,
roles: user_session.roles,
isLoading: false,
isAuthenticated: true,
})
}
if (!access_token) {
setSession({
access_token: '',
user: {},
roles: {},
isLoading: false,
isAuthenticated: false,
})
}
}
useEffect(() => {
// Check session
checkSession()
}, [])
return (
<SessionContext.Provider value={session}>
{children}
</SessionContext.Provider>
)
}
export function useSession() {
return useContext(SessionContext)
}
export default SessionProvider

View file

@ -5,7 +5,8 @@ import Modal from '@components/StyledElements/Modal/Modal'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { unLinkResourcesToUserGroup } from '@services/usergroups/usergroups' import { unLinkResourcesToUserGroup } from '@services/usergroups/usergroups'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Globe, SquareUserRound, Users, UsersRound, X } from 'lucide-react' import { Globe, SquareUserRound, Users, X } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import React from 'react' import React from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
@ -17,13 +18,15 @@ type EditCourseAccessProps = {
function EditCourseAccess(props: EditCourseAccessProps) { function EditCourseAccess(props: EditCourseAccessProps) {
const [error, setError] = React.useState('') const [error, setError] = React.useState('')
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const course = useCourse() as any const course = useCourse() as any
const dispatchCourse = useCourseDispatch() as any const dispatchCourse = useCourseDispatch() as any
const courseStructure = course.courseStructure const courseStructure = course.courseStructure
const { data: usergroups } = useSWR( const { data: usergroups } = useSWR(
courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null, courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const [isPublic, setIsPublic] = React.useState(courseStructure.public) const [isPublic, setIsPublic] = React.useState(courseStructure.public)
@ -109,7 +112,7 @@ function EditCourseAccess(props: EditCourseAccessProps) {
status="info" status="info"
></ConfirmationModal> ></ConfirmationModal>
</div> </div>
{!isPublic ? ( <UserGroupsSection usergroups={usergroups} />) : null} {!isPublic ? (<UserGroupsSection usergroups={usergroups} />) : null}
</div> </div>
</div> </div>
) )
@ -119,9 +122,11 @@ function EditCourseAccess(props: EditCourseAccessProps) {
function UserGroupsSection({ usergroups }: { usergroups: any[] }) { function UserGroupsSection({ usergroups }: { usergroups: any[] }) {
const course = useCourse() as any const course = useCourse() as any
const [userGroupModal, setUserGroupModal] = React.useState(false) const [userGroupModal, setUserGroupModal] = React.useState(false)
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const removeUserGroupLink = async (usergroup_id: number) => { const removeUserGroupLink = async (usergroup_id: number) => {
const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid) const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid,access_token)
if (res.status === 200) { if (res.status === 200) {
toast.success('Successfully unliked from usergroup') toast.success('Successfully unliked from usergroup')
mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`) mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`)

View file

@ -6,7 +6,6 @@ import FormLayout, {
} from '@components/StyledElements/Form/Form' } from '@components/StyledElements/Form/Form'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import { AlertTriangle } from 'lucide-react' import { AlertTriangle } from 'lucide-react'
import * as Switch from '@radix-ui/react-switch'
import * as Form from '@radix-ui/react-form' import * as Form from '@radix-ui/react-form'
import React from 'react' import React from 'react'
import { useCourse, useCourseDispatch } from '../../../Contexts/CourseContext' import { useCourse, useCourseDispatch } from '../../../Contexts/CourseContext'

View file

@ -4,11 +4,13 @@ import { getAPIUrl } from '@services/config/config'
import { updateCourseThumbnail } from '@services/courses/courses' import { updateCourseThumbnail } from '@services/courses/courses'
import { getCourseThumbnailMediaDirectory } from '@services/media/media' import { getCourseThumbnailMediaDirectory } from '@services/media/media'
import { ArrowBigUpDash, UploadCloud } from 'lucide-react' import { ArrowBigUpDash, UploadCloud } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import React from 'react' import React from 'react'
import { mutate } from 'swr' import { mutate } from 'swr'
function ThumbnailUpdate() { function ThumbnailUpdate() {
const course = useCourse() as any const course = useCourse() as any
const session = useLHSession() as any;
const org = useOrg() as any const org = useOrg() as any
const [localThumbnail, setLocalThumbnail] = React.useState(null) as any const [localThumbnail, setLocalThumbnail] = React.useState(null) as any
const [isLoading, setIsLoading] = React.useState(false) as any const [isLoading, setIsLoading] = React.useState(false) as any
@ -20,7 +22,8 @@ function ThumbnailUpdate() {
setIsLoading(true) setIsLoading(true)
const res = await updateCourseThumbnail( const res = await updateCourseThumbnail(
course.courseStructure.course_uuid, course.courseStructure.course_uuid,
file file,
session.data?.tokens?.access_token
) )
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
// wait for 1 second to show loading animation // wait for 1 second to show loading animation

View file

@ -10,6 +10,7 @@ import {
import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs' import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs'
import { revalidateTags } from '@services/utils/ts/requests' import { revalidateTags } from '@services/utils/ts/requests'
import { Layers } from 'lucide-react' import { Layers } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { mutate } from 'swr' import { mutate } from 'swr'
@ -23,6 +24,8 @@ function NewActivityButton(props: NewActivityButtonProps) {
const [newActivityModal, setNewActivityModal] = React.useState(false) const [newActivityModal, setNewActivityModal] = React.useState(false)
const router = useRouter() const router = useRouter()
const course = useCourse() as any const course = useCourse() as any
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const openNewActivityModal = async (chapterId: any) => { const openNewActivityModal = async (chapterId: any) => {
setNewActivityModal(true) setNewActivityModal(true)
@ -38,7 +41,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
props.orgslug, props.orgslug,
{ revalidate: 1800 } { revalidate: 1800 }
) )
await createActivity(activity, props.chapterId, org.org_id) await createActivity(activity, props.chapterId, org.org_id, access_token)
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
setNewActivityModal(false) setNewActivityModal(false)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
@ -52,7 +55,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
activity: any, activity: any,
chapterId: string chapterId: string
) => { ) => {
await createFileActivity(file, type, activity, chapterId) await createFileActivity(file, type, activity, chapterId, access_token)
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
setNewActivityModal(false) setNewActivityModal(false)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
@ -68,7 +71,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
await createExternalVideoActivity( await createExternalVideoActivity(
external_video_data, external_video_data,
activity, activity,
props.chapterId props.chapterId, access_token
) )
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
setNewActivityModal(false) setNewActivityModal(false)
@ -76,7 +79,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
router.refresh() router.refresh()
} }
useEffect(() => {}, [course]) useEffect(() => { }, [course])
return ( return (
<div className="flex justify-center"> <div className="flex justify-center">

View file

@ -12,6 +12,7 @@ import {
Video, Video,
X, X,
} from 'lucide-react' } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
@ -32,6 +33,8 @@ interface ModifiedActivityInterface {
function ActivityElement(props: ActivitiyElementProps) { function ActivityElement(props: ActivitiyElementProps) {
const router = useRouter() const router = useRouter()
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const [modifiedActivity, setModifiedActivity] = React.useState< const [modifiedActivity, setModifiedActivity] = React.useState<
ModifiedActivityInterface | undefined ModifiedActivityInterface | undefined
>(undefined) >(undefined)
@ -41,7 +44,7 @@ function ActivityElement(props: ActivitiyElementProps) {
const activityUUID = props.activity.activity_uuid const activityUUID = props.activity.activity_uuid
async function deleteActivityUI() { async function deleteActivityUI() {
await deleteActivity(props.activity.activity_uuid) await deleteActivity(props.activity.activity_uuid,access_token)
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()
@ -60,7 +63,7 @@ function ActivityElement(props: ActivitiyElementProps) {
content: props.activity.content, content: props.activity.content,
} }
await updateActivity(modifiedActivityCopy, activityUUID) await updateActivity(modifiedActivityCopy, activityUUID,access_token)
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()

View file

@ -16,6 +16,7 @@ import { revalidateTags } from '@services/utils/ts/requests'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { mutate } from 'swr' import { mutate } from 'swr'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type ChapterElementProps = { type ChapterElementProps = {
chapter: any chapter: any
@ -31,6 +32,8 @@ interface ModifiedChapterInterface {
function ChapterElement(props: ChapterElementProps) { function ChapterElement(props: ChapterElementProps) {
const activities = props.chapter.activities || [] const activities = props.chapter.activities || []
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const [modifiedChapter, setModifiedChapter] = React.useState< const [modifiedChapter, setModifiedChapter] = React.useState<
ModifiedChapterInterface | undefined ModifiedChapterInterface | undefined
>(undefined) >(undefined)
@ -41,7 +44,7 @@ function ChapterElement(props: ChapterElementProps) {
const router = useRouter() const router = useRouter()
const deleteChapterUI = async () => { const deleteChapterUI = async () => {
await deleteChapter(props.chapter.id) await deleteChapter(props.chapter.id, access_token)
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()
@ -53,7 +56,7 @@ function ChapterElement(props: ChapterElementProps) {
let modifiedChapterCopy = { let modifiedChapterCopy = {
name: modifiedChapter.chapterName, name: modifiedChapter.chapterName,
} }
await updateChapter(chapterId, modifiedChapterCopy) await updateChapter(chapterId, modifiedChapterCopy, access_token)
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()

View file

@ -15,6 +15,7 @@ import {
import { Hexagon } from 'lucide-react' import { Hexagon } from 'lucide-react'
import Modal from '@components/StyledElements/Modal/Modal' import Modal from '@components/StyledElements/Modal/Modal'
import NewChapterModal from '@components/Objects/Modals/Chapters/NewChapter' import NewChapterModal from '@components/Objects/Modals/Chapters/NewChapter'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type EditCourseStructureProps = { type EditCourseStructureProps = {
orgslug: string orgslug: string
@ -38,6 +39,8 @@ export type OrderPayload =
const EditCourseStructure = (props: EditCourseStructureProps) => { const EditCourseStructure = (props: EditCourseStructureProps) => {
const router = useRouter() const router = useRouter()
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
// Check window availability // Check window availability
const [winReady, setwinReady] = useState(false) const [winReady, setwinReady] = useState(false)
@ -57,7 +60,7 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
// Submit new chapter // Submit new chapter
const submitChapter = async (chapter: any) => { const submitChapter = async (chapter: any) => {
await createChapter(chapter) await createChapter(chapter,access_token)
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()

View file

@ -9,6 +9,7 @@ import { UploadCloud } from 'lucide-react'
import { revalidateTags } from '@services/utils/ts/requests' import { revalidateTags } from '@services/utils/ts/requests'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
interface OrganizationValues { interface OrganizationValues {
name: string name: string
@ -20,7 +21,9 @@ interface OrganizationValues {
function OrgEditGeneral(props: any) { function OrgEditGeneral(props: any) {
const [selectedFile, setSelectedFile] = useState<File | null>(null) const [selectedFile, setSelectedFile] = useState<File | null>(null)
const router = useRouter() const router = useRouter();
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const org = useOrg() as any const org = useOrg() as any
// ... // ...
@ -34,7 +37,7 @@ function OrgEditGeneral(props: any) {
const uploadLogo = async () => { const uploadLogo = async () => {
if (selectedFile) { if (selectedFile) {
let org_id = org.id let org_id = org.id
await uploadOrganizationLogo(org_id, selectedFile) await uploadOrganizationLogo(org_id, selectedFile, access_token)
setSelectedFile(null) // Reset the selected file setSelectedFile(null) // Reset the selected file
await revalidateTags(['organizations'], org.slug) await revalidateTags(['organizations'], org.slug)
router.refresh() router.refresh()
@ -51,14 +54,14 @@ function OrgEditGeneral(props: any) {
const updateOrg = async (values: OrganizationValues) => { const updateOrg = async (values: OrganizationValues) => {
let org_id = org.id let org_id = org.id
await updateOrganization(org_id, values) await updateOrganization(org_id, values, access_token)
// Mutate the org // Mutate the org
await revalidateTags(['organizations'], org.slug) await revalidateTags(['organizations'], org.slug)
router.refresh() router.refresh()
} }
useEffect(() => {}, [org]) useEffect(() => { }, [org])
return ( return (
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5"> <div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">

View file

@ -1,22 +1,21 @@
'use client' 'use client'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useSession } from '@components/Contexts/SessionContext' import { signOut } from 'next-auth/react'
import ToolTip from '@components/StyledElements/Tooltip/Tooltip' import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import LearnHouseDashboardLogo from '@public/dashLogo.png' import LearnHouseDashboardLogo from '@public/dashLogo.png'
import { logout } from '@services/auth/auth'
import { BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react' import { BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import UserAvatar from '../../Objects/UserAvatar' import UserAvatar from '../../Objects/UserAvatar'
import AdminAuthorization from '@components/Security/AdminAuthorization' import AdminAuthorization from '@components/Security/AdminAuthorization'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
function LeftMenu() { function LeftMenu() {
const org = useOrg() as any const org = useOrg() as any
const session = useSession() as any const session = useLHSession() as any
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true)
const route = useRouter()
function waitForEverythingToLoad() { function waitForEverythingToLoad() {
if (org && session) { if (org && session) {
@ -26,9 +25,9 @@ function LeftMenu() {
} }
async function logOutUI() { async function logOutUI() {
const res = await logout() const res = await signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/login?orgslug=' + org.slug) })
if (res) { if (res) {
route.push('/login') getUriWithOrg(org.slug, '/')
} }
} }
@ -123,7 +122,7 @@ function LeftMenu() {
<div className="flex flex-col mx-auto pb-7 space-y-2"> <div className="flex flex-col mx-auto pb-7 space-y-2">
<div className="flex items-center flex-col space-y-2"> <div className="flex items-center flex-col space-y-2">
<ToolTip <ToolTip
content={'@' + session.user.username} content={'@' + session.data.user.username}
slateBlack slateBlack
sideOffset={8} sideOffset={8}
side="right" side="right"
@ -134,7 +133,7 @@ function LeftMenu() {
</ToolTip> </ToolTip>
<div className="flex items-center flex-col space-y-1"> <div className="flex items-center flex-col space-y-1">
<ToolTip <ToolTip
content={session.user.username + "'s Settings"} content={session.data.user.username + "'s Settings"}
slateBlack slateBlack
sideOffset={8} sideOffset={8}
side="right" side="right"

View file

@ -11,9 +11,11 @@ import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { mutate } from 'swr' import { mutate } from 'swr'
import { updateCourse } from '@services/courses/courses' import { updateCourse } from '@services/courses/courses'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function SaveState(props: { orgslug: string }) { function SaveState(props: { orgslug: string }) {
const course = useCourse() as any const course = useCourse() as any
const session = useLHSession() as any;
const router = useRouter() const router = useRouter()
const saved = course ? course.isSaved : true const saved = course ? course.isSaved : true
const dispatchCourse = useCourseDispatch() as any const dispatchCourse = useCourseDispatch() as any
@ -37,7 +39,8 @@ function SaveState(props: { orgslug: string }) {
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
await updateCourseOrderStructure( await updateCourseOrderStructure(
course.courseStructure.course_uuid, course.courseStructure.course_uuid,
course.courseOrder course.courseOrder,
session.data?.tokens?.access_token
) )
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()
@ -49,7 +52,8 @@ function SaveState(props: { orgslug: string }) {
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`) mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
await updateCourse( await updateCourse(
course.courseStructure.course_uuid, course.courseStructure.course_uuid,
course.courseStructure course.courseStructure,
session.data?.tokens?.access_token
) )
await revalidateTags(['courses'], props.orgslug) await revalidateTags(['courses'], props.orgslug)
router.refresh() router.refresh()

View file

@ -1,7 +1,8 @@
'use client';
import { updateProfile } from '@services/settings/profile' import { updateProfile } from '@services/settings/profile'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Formik, Form, Field } from 'formik' import { Formik, Form, Field } from 'formik'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { import {
ArrowBigUpDash, ArrowBigUpDash,
Check, Check,
@ -13,7 +14,8 @@ import UserAvatar from '@components/Objects/UserAvatar'
import { updateUserAvatar } from '@services/users/users' import { updateUserAvatar } from '@services/users/users'
function UserEditGeneral() { function UserEditGeneral() {
const session = useSession() as any const session = useLHSession() as any;
const access_token = session.data.tokens.access_token;
const [localAvatar, setLocalAvatar] = React.useState(null) as any const [localAvatar, setLocalAvatar] = React.useState(null) as any
const [isLoading, setIsLoading] = React.useState(false) as any const [isLoading, setIsLoading] = React.useState(false) as any
const [error, setError] = React.useState() as any const [error, setError] = React.useState() as any
@ -23,7 +25,7 @@ function UserEditGeneral() {
const file = event.target.files[0] const file = event.target.files[0]
setLocalAvatar(file) setLocalAvatar(file)
setIsLoading(true) setIsLoading(true)
const res = await updateUserAvatar(session.user.user_uuid, file) const res = await updateUserAvatar(session.data.user_uuid, file, access_token)
// wait for 1 second to show loading animation // wait for 1 second to show loading animation
await new Promise((r) => setTimeout(r, 1500)) await new Promise((r) => setTimeout(r, 1500))
if (res.success === false) { if (res.success === false) {
@ -35,24 +37,24 @@ function UserEditGeneral() {
} }
} }
useEffect(() => {}, [session, session.user]) useEffect(() => { }, [session, session.data])
return ( return (
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5"> <div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
{session.user && ( {session.data.user && (
<Formik <Formik
enableReinitialize enableReinitialize
initialValues={{ initialValues={{
username: session.user.username, username: session.data.user.username,
first_name: session.user.first_name, first_name: session.data.user.first_name,
last_name: session.user.last_name, last_name: session.data.user.last_name,
email: session.user.email, email: session.data.user.email,
bio: session.user.bio, bio: session.data.user.bio,
}} }}
onSubmit={(values, { setSubmitting }) => { onSubmit={(values, { setSubmitting }) => {
setTimeout(() => { setTimeout(() => {
setSubmitting(false) setSubmitting(false)
updateProfile(values, session.user.id) updateProfile(values, session.data.user.id, access_token)
}, 400) }, 400)
}} }}
> >

View file

@ -1,17 +1,18 @@
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { updatePassword } from '@services/settings/password' import { updatePassword } from '@services/settings/password'
import { Formik, Form, Field } from 'formik' import { Formik, Form, Field } from 'formik'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
function UserEditPassword() { function UserEditPassword() {
const session = useSession() as any const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const updatePasswordUI = async (values: any) => { const updatePasswordUI = async (values: any) => {
let user_id = session.user.id let user_id = session.data.user.id
await updatePassword(user_id, values) await updatePassword(user_id, values, access_token)
} }
useEffect(() => {}, [session]) useEffect(() => { }, [session])
return ( return (
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5"> <div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">

View file

@ -3,27 +3,28 @@ import PageLoading from '@components/Objects/Loaders/PageLoading'
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal' import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import { getAPIUrl, getUriWithOrg } from '@services/config/config' import { getAPIUrl, getUriWithOrg } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Globe, Shield, Ticket, User, UserSquare, Users, X } from 'lucide-react' import { Globe, Ticket, UserSquare, Users, X } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import {
changeSignupMechanism, changeSignupMechanism,
createInviteCode,
deleteInviteCode, deleteInviteCode,
} from '@services/organizations/invites' } from '@services/organizations/invites'
import Toast from '@components/StyledElements/Toast/Toast'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import Modal from '@components/StyledElements/Modal/Modal' import Modal from '@components/StyledElements/Modal/Modal'
import OrgInviteCodeGenerate from '@components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate' import OrgInviteCodeGenerate from '@components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function OrgAccess() { function OrgAccess() {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const { data: invites } = useSWR( const { data: invites } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null, org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const [isLoading, setIsLoading] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false)
const [joinMethod, setJoinMethod] = React.useState('closed') const [joinMethod, setJoinMethod] = React.useState('closed')
@ -40,10 +41,8 @@ function OrgAccess() {
} }
} }
async function deleteInvite(invite: any) { async function deleteInvite(invite: any) {
let res = await deleteInviteCode(org.id, invite.invite_code_uuid) let res = await deleteInviteCode(org.id, invite.invite_code_uuid, access_token)
if (res.status == 200) { if (res.status == 200) {
mutate(`${getAPIUrl()}orgs/${org.id}/invites`) mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
} else { } else {
@ -52,7 +51,7 @@ function OrgAccess() {
} }
async function changeJoinMethod(method: 'open' | 'inviteOnly') { async function changeJoinMethod(method: 'open' | 'inviteOnly') {
let res = await changeSignupMechanism(org.id, method) let res = await changeSignupMechanism(org.id, method, access_token)
if (res.status == 200) { if (res.status == 200) {
router.refresh() router.refresh()
mutate(`${getAPIUrl()}orgs/slug/${org?.slug}`) mutate(`${getAPIUrl()}orgs/slug/${org?.slug}`)

View file

@ -1,4 +1,5 @@
'use client' 'use client'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import AddUserGroup from '@components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup' import AddUserGroup from '@components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup'
import ManageUsers from '@components/Objects/Modals/Dash/OrgUserGroups/ManageUsers' import ManageUsers from '@components/Objects/Modals/Dash/OrgUserGroups/ManageUsers'
@ -14,17 +15,19 @@ import useSWR, { mutate } from 'swr'
function OrgUserGroups() { function OrgUserGroups() {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [userGroupManagementModal, setUserGroupManagementModal] = React.useState(false) const [userGroupManagementModal, setUserGroupManagementModal] = React.useState(false)
const [createUserGroupModal, setCreateUserGroupModal] = React.useState(false) const [createUserGroupModal, setCreateUserGroupModal] = React.useState(false)
const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any
const { data: usergroups } = useSWR( const { data: usergroups } = useSWR(
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null, org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const deleteUserGroupUI = async (usergroup_id: any) => { const deleteUserGroupUI = async (usergroup_id: any) => {
const res = await deleteUserGroup(usergroup_id) const res = await deleteUserGroup(usergroup_id, access_token)
if (res.status == 200) { if (res.status == 200) {
mutate(`${getAPIUrl()}usergroups/org/${org.id}`) mutate(`${getAPIUrl()}usergroups/org/${org.id}`)
} }

View file

@ -1,3 +1,4 @@
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import PageLoading from '@components/Objects/Loaders/PageLoading' import PageLoading from '@components/Objects/Loaders/PageLoading'
import RolesUpdate from '@components/Objects/Modals/Dash/OrgUsers/RolesUpdate' import RolesUpdate from '@components/Objects/Modals/Dash/OrgUsers/RolesUpdate'
@ -14,9 +15,11 @@ import useSWR, { mutate } from 'swr'
function OrgUsers() { function OrgUsers() {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const { data: orgUsers } = useSWR( const { data: orgUsers } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/users` : null, org ? `${getAPIUrl()}orgs/${org?.id}/users` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const [rolesModal, setRolesModal] = React.useState(false) const [rolesModal, setRolesModal] = React.useState(false)
const [selectedUser, setSelectedUser] = React.useState(null) as any const [selectedUser, setSelectedUser] = React.useState(null) as any
@ -28,7 +31,7 @@ function OrgUsers() {
} }
const handleRemoveUser = async (user_id: any) => { const handleRemoveUser = async (user_id: any) => {
const res = await removeUserFromOrg(org.id, user_id) const res = await removeUserFromOrg(org.id, user_id,access_token)
if (res.status === 200) { if (res.status === 200) {
await mutate(`${getAPIUrl()}orgs/${org.id}/users`) await mutate(`${getAPIUrl()}orgs/${org.id}/users`)
} else { } else {
@ -39,7 +42,6 @@ function OrgUsers() {
useEffect(() => { useEffect(() => {
if (orgUsers) { if (orgUsers) {
setIsLoading(false) setIsLoading(false)
console.log(orgUsers)
} }
}, [org, orgUsers]) }, [org, orgUsers])

View file

@ -1,3 +1,4 @@
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import PageLoading from '@components/Objects/Loaders/PageLoading' import PageLoading from '@components/Objects/Loaders/PageLoading'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
@ -5,20 +6,22 @@ import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { inviteBatchUsers } from '@services/organizations/invites' import { inviteBatchUsers } from '@services/organizations/invites'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Info, Shield, UserPlus } from 'lucide-react' import { Info, UserPlus } from 'lucide-react'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
function OrgUsersAdd() { function OrgUsersAdd() {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [isLoading, setIsLoading] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false)
const [invitedUsers, setInvitedUsers] = React.useState(''); const [invitedUsers, setInvitedUsers] = React.useState('');
const [selectedInviteCode, setSelectedInviteCode] = React.useState(''); const [selectedInviteCode, setSelectedInviteCode] = React.useState('');
async function sendInvites() { async function sendInvites() {
setIsLoading(true) setIsLoading(true)
let res = await inviteBatchUsers(org.id, invitedUsers, selectedInviteCode) let res = await inviteBatchUsers(org.id, invitedUsers, selectedInviteCode,access_token)
if (res.status == 200) { if (res.status == 200) {
mutate(`${getAPIUrl()}orgs/${org?.id}/invites/users`) mutate(`${getAPIUrl()}orgs/${org?.id}/invites/users`)
setIsLoading(false) setIsLoading(false)
@ -31,18 +34,17 @@ function OrgUsersAdd() {
const { data: invites } = useSWR( const { data: invites } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null, org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
const { data: invited_users } = useSWR( const { data: invited_users } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/invites/users` : null, org ? `${getAPIUrl()}orgs/${org?.id}/invites/users` : null,
swrFetcher (url) => swrFetcher(url, access_token)
) )
useEffect(() => { useEffect(() => {
if (invites) { if (invites) {
setSelectedInviteCode(invites?.[0]?.invite_code_uuid) setSelectedInviteCode(invites?.[0]?.invite_code_uuid)
} }
console.log('dev,',selectedInviteCode)
} }
, [invites, invited_users]) , [invites, invited_users])

View file

@ -1,40 +1,43 @@
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext';
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext';
import { useEffect } from 'react' import { useEffect, useState, useMemo } from 'react';
function useAdminStatus() {
const session = useSession() as any
const org = useOrg() as any
// If session is not loaded, redirect to login
useEffect(() => {
if (session.isLoading) {
return
}
}
, [session])
const isUserAdmin = () => {
if (session.isAuthenticated) {
const isAdmin = session.roles.some((role: any) => {
return (
role.org.id === org.id &&
(role.role.id === 1 ||
role.role.id === 2 ||
role.role.role_uuid === 'role_global_admin' ||
role.role.role_uuid === 'role_global_maintainer')
)
})
return isAdmin
}
return false
}
// Return the user admin status
return isUserAdmin()
interface Role {
org: { id: number };
role: { id: number; role_uuid: string };
} }
export default useAdminStatus function useAdminStatus() {
const session = useLHSession() as any;
const org = useOrg() as any;
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const userRoles = useMemo(() => session?.data?.roles || [], [session?.data?.roles]);
useEffect(() => {
if (session.status === 'authenticated' && org?.id) {
const isAdminVar = userRoles.some((role: Role) => {
return (
role.org.id === org.id &&
(
role.role.id === 1 ||
role.role.id === 2 ||
role.role.role_uuid === 'role_global_admin' ||
role.role.role_uuid === 'role_global_maintainer'
)
);
});
setIsAdmin(isAdminVar);
setLoading(false); // Set loading to false once the status is determined
} else {
setIsAdmin(false);
setLoading(false); // Set loading to false if not authenticated or org not found
}
}, [session.status, userRoles, org.id]);
return { isAdmin, loading };
}
export default useAdminStatus;

View file

@ -1,4 +1,4 @@
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { import {
sendActivityAIChatMessage, sendActivityAIChatMessage,
startActivityAIChatSession, startActivityAIChatSession,
@ -74,7 +74,8 @@ type ActivityChatMessageBoxProps = {
} }
function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) { function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
const session = useSession() as any const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
const dispatchAIChatBot = useAIChatBotDispatch() as any const dispatchAIChatBot = useAIChatBotDispatch() as any
@ -115,7 +116,8 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
const response = await sendActivityAIChatMessage( const response = await sendActivityAIChatMessage(
message, message,
aiChatBotState.aichat_uuid, aiChatBotState.aichat_uuid,
props.activity.activity_uuid props.activity.activity_uuid,
access_token
) )
if (response.success == false) { if (response.success == false) {
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
@ -143,8 +145,9 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
}) })
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
const response = await startActivityAIChatSession( const response = await startActivityAIChatSession(
message, message,access_token,
props.activity.activity_uuid props.activity.activity_uuid
) )
if (response.success == false) { if (response.success == false) {
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
@ -219,13 +222,11 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
/> />
</div> </div>
<div <div
className={`flex space-x-2 items-center -ml-[100px] ${ className={`flex space-x-2 items-center -ml-[100px] ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
}`} }`}
> >
<Image <Image
className={`outline outline-1 outline-neutral-200/20 rounded-lg ${ className={`outline outline-1 outline-neutral-200/20 rounded-lg ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
}`} }`}
width={24} width={24}
src={learnhouseAI_icon} src={learnhouseAI_icon}
@ -244,8 +245,7 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
</div> </div>
</div> </div>
<div <div
className={`w-100 h-0.5 bg-white/5 rounded-full mx-auto mb-3 ${ className={`w-100 h-0.5 bg-white/5 rounded-full mx-auto mb-3 ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
}`} }`}
></div> ></div>
{aiChatBotState.messages.length > 0 && {aiChatBotState.messages.length > 0 &&
@ -328,7 +328,7 @@ type AIMessageProps = {
} }
function AIMessage(props: AIMessageProps) { function AIMessage(props: AIMessageProps) {
const session = useSession() as any const session = useLHSession() as any
const words = props.message.message.split(' ') const words = props.message.message.split(' ')
@ -378,7 +378,7 @@ const AIMessagePlaceHolder = (props: {
activity_uuid: string activity_uuid: string
sendMessage: any sendMessage: any
}) => { }) => {
const session = useSession() as any const session = useLHSession() as any
const [feedbackModal, setFeedbackModal] = React.useState(false) const [feedbackModal, setFeedbackModal] = React.useState(false)
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
@ -409,7 +409,7 @@ const AIMessagePlaceHolder = (props: {
<span className="items-center">Hello</span> <span className="items-center">Hello</span>
<span className="capitalize flex space-x-2 items-center"> <span className="capitalize flex space-x-2 items-center">
<UserAvatar rounded="rounded-lg" border="border-2" width={35} /> <UserAvatar rounded="rounded-lg" border="border-2" width={35} />
<span>{session.user.username},</span> <span>{session.data.user.username},</span>
</span> </span>
<span>how can we help today ?</span> <span>how can we help today ?</span>
</p> </p>

View file

@ -12,7 +12,6 @@ function DocumentPdfActivity({
const org = useOrg() as any const org = useOrg() as any
React.useEffect(() => { React.useEffect(() => {
console.log(activity)
}, [activity, org]) }, [activity, org])
return ( return (

View file

@ -15,6 +15,7 @@ import {
startActivityAIChatSession, startActivityAIChatSession,
} from '@services/ai/ai' } from '@services/ai/ai'
import useGetAIFeatures from '../../../../AI/Hooks/useGetAIFeatures' import useGetAIFeatures from '../../../../AI/Hooks/useGetAIFeatures'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type AICanvaToolkitProps = { type AICanvaToolkitProps = {
editor: Editor editor: Editor
@ -92,6 +93,8 @@ function AIActionButton(props: {
label: string label: string
activity: any activity: any
}) { }) {
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const dispatchAIChatBot = useAIChatBotDispatch() as any const dispatchAIChatBot = useAIChatBotDispatch() as any
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
@ -132,7 +135,7 @@ function AIActionButton(props: {
const response = await sendActivityAIChatMessage( const response = await sendActivityAIChatMessage(
message, message,
aiChatBotState.aichat_uuid, aiChatBotState.aichat_uuid,
props.activity.activity_uuid props.activity.activity_uuid, access_token
) )
if (response.success == false) { if (response.success == false) {
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
@ -160,8 +163,7 @@ function AIActionButton(props: {
}) })
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
const response = await startActivityAIChatSession( const response = await startActivityAIChatSession(
message, message, access_token
props.activity.activity_uuid
) )
if (response.success == false) { if (response.success == false) {
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })

View file

@ -7,22 +7,6 @@ function VideoActivity({ activity, course }: { activity: any; course: any }) {
const org = useOrg() as any const org = useOrg() as any
const [videoId, setVideoId] = React.useState('') const [videoId, setVideoId] = React.useState('')
function getYouTubeEmbed(url: any) {
// Extract video ID from the YouTube URL
var videoId = url.match(
/(?:\?v=|\/embed\/|\/\d\/|\/vi\/|\/v\/|https?:\/\/(?:www\.)?youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([^#\&\?\/]+)/
)[1]
// Create the embed object
var embedObject = {
videoId: videoId,
width: 560,
height: 315,
}
return embedObject
}
React.useEffect(() => { React.useEffect(() => {
if (activity && activity.content && activity.content.uri) { if (activity && activity.content && activity.content.uri) {
var getYouTubeID = require('get-youtube-id'); var getYouTubeID = require('get-youtube-id');

View file

@ -20,12 +20,15 @@ import toast from 'react-hot-toast'
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal' import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import { useLHSession } from '@components/Contexts/LHSessionContext'
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
function CourseUpdates() { function CourseUpdates() {
const course = useCourse() as any; const course = useCourse() as any;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, swrFetcher) const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, (url) => swrFetcher(url, access_token))
const [isModelOpen, setIsModelOpen] = React.useState(false) const [isModelOpen, setIsModelOpen] = React.useState(false)
function handleModelOpen() { function handleModelOpen() {
@ -35,7 +38,6 @@ function CourseUpdates() {
// if user clicks outside the model, close the model // if user clicks outside the model, close the model
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
function handleClickOutside(event: any) { function handleClickOutside(event: any) {
console.log(event.target.id)
if (event.target.closest('.bg-white') || event.target.id === 'delete-update-button') return; if (event.target.closest('.bg-white') || event.target.id === 'delete-update-button') return;
setIsModelOpen(false); setIsModelOpen(false);
} }
@ -71,7 +73,7 @@ function CourseUpdates() {
const UpdatesSection = () => { const UpdatesSection = () => {
const [selectedView, setSelectedView] = React.useState('list') const [selectedView, setSelectedView] = React.useState('list')
const isAdmin = useAdminStatus() as boolean; const adminStatus = useAdminStatus() ;
return ( return (
<div className='bg-white/95 backdrop-blur-md nice-shadow rounded-lg w-[700px] overflow-hidden'> <div className='bg-white/95 backdrop-blur-md nice-shadow rounded-lg w-[700px] overflow-hidden'>
<div className='bg-gray-50/70 flex justify-between outline outline-1 rounded-lg outline-neutral-200/40'> <div className='bg-gray-50/70 flex justify-between outline outline-1 rounded-lg outline-neutral-200/40'>
@ -80,7 +82,7 @@ const UpdatesSection = () => {
<span>Updates</span> <span>Updates</span>
</div> </div>
{isAdmin && <div {adminStatus.isAdmin && <div
onClick={() => setSelectedView('new')} onClick={() => setSelectedView('new')}
className='py-2 px-4 space-x-2 items-center flex cursor-pointer text-xs font-medium hover:bg-gray-200 bg-gray-100 outline outline-1 outline-neutral-200/40'> className='py-2 px-4 space-x-2 items-center flex cursor-pointer text-xs font-medium hover:bg-gray-200 bg-gray-100 outline outline-1 outline-neutral-200/40'>
<PencilLine size={14} /> <PencilLine size={14} />
@ -98,6 +100,7 @@ const UpdatesSection = () => {
const NewUpdateForm = ({ setSelectedView }: any) => { const NewUpdateForm = ({ setSelectedView }: any) => {
const org = useOrg() as any; const org = useOrg() as any;
const course = useCourse() as any; const course = useCourse() as any;
const session = useLHSession() as any;
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}
@ -124,7 +127,7 @@ const NewUpdateForm = ({ setSelectedView }: any) => {
course_uuid: course.courseStructure.course_uuid, course_uuid: course.courseStructure.course_uuid,
org_id: org.id org_id: org.id
} }
const res = await createCourseUpdate(body) const res = await createCourseUpdate(body, session.data?.tokens?.access_token)
if (res.status === 200) { if (res.status === 200) {
toast.success('Update added successfully') toast.success('Update added successfully')
setSelectedView('list') setSelectedView('list')
@ -192,12 +195,14 @@ const NewUpdateForm = ({ setSelectedView }: any) => {
const UpdatesListView = () => { const UpdatesListView = () => {
const course = useCourse() as any; const course = useCourse() as any;
const isAdmin = useAdminStatus() as boolean; const adminStatus = useAdminStatus() ;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, swrFetcher) const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure?.course_uuid}/updates`, (url) => swrFetcher(url, access_token))
return ( return (
<div className='px-5 bg-white overflow-y-auto' style={{ maxHeight: '400px' }}> <div className='px-5 bg-white overflow-y-auto' style={{ maxHeight: '400px' }}>
{updates && updates.map((update: any) => ( {updates && !adminStatus.loading && updates.map((update: any) => (
<div key={update.id} className='py-2 border-b border-neutral-200 antialiased'> <div key={update.id} className='py-2 border-b border-neutral-200 antialiased'>
<div className='font-bold text-gray-500 flex space-x-2 items-center justify-between '> <div className='font-bold text-gray-500 flex space-x-2 items-center justify-between '>
<div className='flex space-x-2 items-center'> <div className='flex space-x-2 items-center'>
@ -208,7 +213,7 @@ const UpdatesListView = () => {
{dayjs(update.creation_date).fromNow()} {dayjs(update.creation_date).fromNow()}
</span> </span>
</div> </div>
{isAdmin && <DeleteUpdateButton update={update} />}</div> {adminStatus.isAdmin && !adminStatus.loading && <DeleteUpdateButton update={update} />}</div>
<div className='text-gray-600'>{update.content}</div> <div className='text-gray-600'>{update.content}</div>
</div> </div>
))} ))}
@ -223,11 +228,12 @@ const UpdatesListView = () => {
} }
const DeleteUpdateButton = ({ update }: any) => { const DeleteUpdateButton = ({ update }: any) => {
const session = useLHSession() as any;
const course = useCourse() as any; const course = useCourse() as any;
const org = useOrg() as any; const org = useOrg() as any;
const handleDelete = async () => { const handleDelete = async () => {
const res = await deleteCourseUpdate(course.courseStructure.course_uuid, update.courseupdate_uuid) const res = await deleteCourseUpdate(course.courseStructure.course_uuid, update.courseupdate_uuid, session.data?.tokens?.access_token)
if (res.status === 200) { if (res.status === 200) {
toast.success('Update deleted successfully') toast.success('Update deleted successfully')
mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`) mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)

View file

@ -24,6 +24,7 @@ import {
startActivityAIChatSession, startActivityAIChatSession,
} from '@services/ai/ai' } from '@services/ai/ai'
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures' import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type AIEditorToolkitProps = { type AIEditorToolkitProps = {
editor: Editor editor: Editor
@ -141,6 +142,8 @@ function AIEditorToolkit(props: AIEditorToolkitProps) {
const UserFeedbackModal = (props: AIEditorToolkitProps) => { const UserFeedbackModal = (props: AIEditorToolkitProps) => {
const dispatchAIEditor = useAIEditorDispatch() as any const dispatchAIEditor = useAIEditorDispatch() as any
const aiEditorState = useAIEditor() as AIEditorStateTypes const aiEditorState = useAIEditor() as AIEditorStateTypes
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
await dispatchAIEditor({ await dispatchAIEditor({
@ -159,7 +162,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
const response = await sendActivityAIChatMessage( const response = await sendActivityAIChatMessage(
message, message,
aiEditorState.aichat_uuid, aiEditorState.aichat_uuid,
props.activity.activity_uuid props.activity.activity_uuid, access_token
) )
if (response.success === false) { if (response.success === false) {
await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' })
@ -191,7 +194,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
}) })
await dispatchAIEditor({ type: 'setIsWaitingForResponse' }) await dispatchAIEditor({ type: 'setIsWaitingForResponse' })
const response = await startActivityAIChatSession( const response = await startActivityAIChatSession(
message, message, access_token,
props.activity.activity_uuid props.activity.activity_uuid
) )
if (response.success === false) { if (response.success === false) {

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import UserAvatar from '../UserAvatar' import UserAvatar from '../UserAvatar'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { getUserAvatarMediaDirectory } from '@services/media/media'; import { getUserAvatarMediaDirectory } from '@services/media/media';
import { getCollaborationServerUrl } from '@services/config/config'; import { getCollaborationServerUrl } from '@services/config/config';
import { useOrg } from '@components/Contexts/OrgContext'; import { useOrg } from '@components/Contexts/OrgContext';
@ -11,7 +11,7 @@ type ActiveAvatarsProps = {
} }
function ActiveAvatars(props: ActiveAvatarsProps) { function ActiveAvatars(props: ActiveAvatarsProps) {
const session = useSession() as any; const session = useLHSession() as any;
const org = useOrg() as any; const org = useOrg() as any;
const [activeUsers, setActiveUsers] = useState({} as any); const [activeUsers, setActiveUsers] = useState({} as any);
@ -27,11 +27,11 @@ function ActiveAvatars(props: ActiveAvatarsProps) {
}); });
// Remove the current user from the list // Remove the current user from the list
delete users[session.user.user_uuid]; delete users[session.data.user.user_uuid];
setActiveUsers(users); setActiveUsers(users);
} }
, [props.mouseMovements, session.user, org]); , [props.mouseMovements, session.data.user, org]);
return ( return (
@ -50,7 +50,7 @@ function ActiveAvatars(props: ActiveAvatarsProps) {
<div className="h-2 w-2 rounded-full" style={{ position: 'absolute', bottom: -5, right: 16, backgroundColor: props.mouseMovements[key].color }} /> <div className="h-2 w-2 rounded-full" style={{ position: 'absolute', bottom: -5, right: 16, backgroundColor: props.mouseMovements[key].color }} />
</div> </div>
))} ))}
{session.isAuthenticated && ( {session.status && (
<div className='z-50'> <div className='z-50'>
<UserAvatar <UserAvatar
width={40} width={40}

View file

@ -41,11 +41,9 @@ import html from 'highlight.js/lib/languages/xml'
import python from 'highlight.js/lib/languages/python' import python from 'highlight.js/lib/languages/python'
import java from 'highlight.js/lib/languages/java' import java from 'highlight.js/lib/languages/java'
import { CourseProvider } from '@components/Contexts/CourseContext' import { CourseProvider } from '@components/Contexts/CourseContext'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import AIEditorToolkit from './AI/AIEditorToolkit' import AIEditorToolkit from './AI/AIEditorToolkit'
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures' import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
import UserAvatar from '../UserAvatar'
import randomColor from 'randomcolor'
import Collaboration from '@tiptap/extension-collaboration' import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import ActiveAvatars from './ActiveAvatars' import ActiveAvatars from './ActiveAvatars'
@ -65,7 +63,7 @@ interface Editor {
} }
function Editor(props: Editor) { function Editor(props: Editor) {
const session = useSession() as any const session = useLHSession() as any
const dispatchAIEditor = useAIEditorDispatch() as any const dispatchAIEditor = useAIEditorDispatch() as any
const aiEditorState = useAIEditor() as AIEditorStateTypes const aiEditorState = useAIEditor() as AIEditorStateTypes
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'editor' }) const is_ai_feature_enabled = useGetAIFeatures({ feature: 'editor' })
@ -145,7 +143,7 @@ function Editor(props: Editor) {
CollaborationCursor.configure({ CollaborationCursor.configure({
provider: props.hocuspocusProvider, provider: props.hocuspocusProvider,
user: { user: {
name: props.session.user.first_name + ' ' + props.session.user.last_name, name: props.session.data.user.first_name + ' ' + props.session.data.user.last_name,
color: props.userRandomColor, color: props.userRandomColor,
}, },
}), }),

View file

@ -5,7 +5,7 @@ import { updateActivity } from '@services/courses/activities'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
import { OrgProvider } from '@components/Contexts/OrgContext' import { OrgProvider } from '@components/Contexts/OrgContext'
import { useSession } from '@components/Contexts/SessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
// Collaboration // Collaboration
import { HocuspocusProvider } from '@hocuspocus/provider' import { HocuspocusProvider } from '@hocuspocus/provider'
@ -24,7 +24,8 @@ interface EditorWrapperProps {
} }
function EditorWrapper(props: EditorWrapperProps): JSX.Element { function EditorWrapper(props: EditorWrapperProps): JSX.Element {
const session = useSession() as any const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
// Define provider in the state // Define provider in the state
const [provider, setProvider] = React.useState<HocuspocusProvider | null>(null); const [provider, setProvider] = React.useState<HocuspocusProvider | null>(null);
const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string) const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string)
@ -57,7 +58,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
document.addEventListener("mousemove", (event) => { document.addEventListener("mousemove", (event) => {
// Share any information you like // Share any information you like
provider?.setAwarenessField("userMouseMovement", { provider?.setAwarenessField("userMouseMovement", {
user: session.user, user: session.data.user,
mouseX: event.clientX, mouseX: event.clientX,
mouseY: event.clientY, mouseY: event.clientY,
color: thisPageColor, color: thisPageColor,
@ -72,14 +73,14 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
provider?.setAwarenessField("savings_states", { provider?.setAwarenessField("savings_states", {
[session.user.user_uuid]: { [session.data.user.user_uuid]: {
status: 'action_save', status: 'action_save',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
user: session.user user: session.data.user
} }
}); });
toast.promise(updateActivity(activity, activity.activity_uuid), { toast.promise(updateActivity(activity, activity.activity_uuid,access_token), {
loading: 'Saving...', loading: 'Saving...',
success: <b>Activity saved!</b>, success: <b>Activity saved!</b>,
error: <b>Could not save.</b>, error: <b>Could not save.</b>,

View file

@ -9,11 +9,14 @@ import { getActivityBlockMediaDirectory } from '@services/media/media'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useCourse } from '@components/Contexts/CourseContext' import { useCourse } from '@components/Contexts/CourseContext'
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext' import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function ImageBlockComponent(props: any) { function ImageBlockComponent(props: any) {
const org = useOrg() as any const org = useOrg() as any
const course = useCourse() as any const course = useCourse() as any
const editorState = useEditorProvider() as any const editorState = useEditorProvider() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const isEditable = editorState.isEditable const isEditable = editorState.isEditable
const [image, setImage] = React.useState(null) const [image, setImage] = React.useState(null)
@ -36,7 +39,7 @@ function ImageBlockComponent(props: any) {
setIsLoading(true) setIsLoading(true)
let object = await uploadNewImageFile( let object = await uploadNewImageFile(
image, image,
props.extension.options.activity.activity_uuid props.extension.options.activity.activity_uuid,access_token
) )
setIsLoading(false) setIsLoading(false)
setblockObject(object) setblockObject(object)

View file

@ -8,10 +8,13 @@ import { getActivityBlockMediaDirectory } from '@services/media/media'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useCourse } from '@components/Contexts/CourseContext' import { useCourse } from '@components/Contexts/CourseContext'
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext' import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function PDFBlockComponent(props: any) { function PDFBlockComponent(props: any) {
const org = useOrg() as any const org = useOrg() as any
const course = useCourse() as any const course = useCourse() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [pdf, setPDF] = React.useState(null) const [pdf, setPDF] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false)
const [blockObject, setblockObject] = React.useState( const [blockObject, setblockObject] = React.useState(
@ -32,7 +35,7 @@ function PDFBlockComponent(props: any) {
setIsLoading(true) setIsLoading(true)
let object = await uploadNewPDFFile( let object = await uploadNewPDFFile(
pdf, pdf,
props.extension.options.activity.activity_uuid props.extension.options.activity.activity_uuid, access_token
) )
setIsLoading(false) setIsLoading(false)
setblockObject(object) setblockObject(object)
@ -41,7 +44,7 @@ function PDFBlockComponent(props: any) {
}) })
} }
useEffect(() => {}, [course, org]) useEffect(() => { }, [course, org])
return ( return (
<NodeViewWrapper className="block-pdf"> <NodeViewWrapper className="block-pdf">

View file

@ -86,10 +86,8 @@ function QuizBlockComponent(props: any) {
if (allCorrect) { if (allCorrect) {
setSubmissionMessage('All answers are correct!') setSubmissionMessage('All answers are correct!')
console.log('All answers are correct!')
} else { } else {
setSubmissionMessage('Some answers are incorrect!') setSubmissionMessage('Some answers are incorrect!')
console.log('Some answers are incorrect!')
} }
} }

View file

@ -8,6 +8,7 @@ import { UploadIcon } from '@radix-ui/react-icons'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useCourse } from '@components/Contexts/CourseContext' import { useCourse } from '@components/Contexts/CourseContext'
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext' import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function VideoBlockComponents(props: any) { function VideoBlockComponents(props: any) {
const org = useOrg() as any const org = useOrg() as any
@ -15,6 +16,8 @@ function VideoBlockComponents(props: any) {
const editorState = useEditorProvider() as any const editorState = useEditorProvider() as any
const isEditable = editorState.isEditable const isEditable = editorState.isEditable
const [video, setVideo] = React.useState(null) const [video, setVideo] = React.useState(null)
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [isLoading, setIsLoading] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false)
const [blockObject, setblockObject] = React.useState( const [blockObject, setblockObject] = React.useState(
props.node.attrs.blockObject props.node.attrs.blockObject
@ -32,7 +35,7 @@ function VideoBlockComponents(props: any) {
setIsLoading(true) setIsLoading(true)
let object = await uploadNewVideoFile( let object = await uploadNewVideoFile(
video, video,
props.extension.options.activity.activity_uuid props.extension.options.activity.activity_uuid, access_token
) )
setIsLoading(false) setIsLoading(false)
setblockObject(object) setblockObject(object)
@ -41,7 +44,7 @@ function VideoBlockComponents(props: any) {
}) })
} }
useEffect(() => {}, [course, org]) useEffect(() => { }, [course, org])
return ( return (
<NodeViewWrapper className="block-video"> <NodeViewWrapper className="block-video">
@ -98,7 +101,7 @@ function VideoBlockComponents(props: any) {
) )
} }
const BlockVideoWrapper = styled.div` const BlockVideoWrapper = styled.div`
//border: ${(props) => border: ${(props) =>
props.contentEditable ? '2px dashed #713f1117' : 'none'}; props.contentEditable ? '2px dashed #713f1117' : 'none'};
// center // center

View file

@ -1,21 +1,19 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import Link from 'next/link' import Link from 'next/link'
import { getAPIUrl, getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { HeaderProfileBox } from '@components/Security/HeaderProfileBox' import { HeaderProfileBox } from '@components/Security/HeaderProfileBox'
import MenuLinks from './MenuLinks' import MenuLinks from './MenuLinks'
import { getOrgLogoMediaDirectory } from '@services/media/media' import { getOrgLogoMediaDirectory } from '@services/media/media'
import useSWR from 'swr' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { swrFetcher } from '@services/utils/ts/requests' import { useOrg } from '@components/Contexts/OrgContext'
export const Menu = (props: any) => { export const Menu = (props: any) => {
const orgslug = props.orgslug const orgslug = props.orgslug
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const [feedbackModal, setFeedbackModal] = React.useState(false) const [feedbackModal, setFeedbackModal] = React.useState(false)
const { const org = useOrg() as any;
data: org,
error: error,
isLoading,
} = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher)
function closeFeedbackModal() { function closeFeedbackModal() {
setFeedbackModal(false) setFeedbackModal(false)

View file

@ -15,9 +15,11 @@ import React, { useState } from 'react'
import { BarLoader } from 'react-spinners' import { BarLoader } from 'react-spinners'
import { revalidateTags } from '@services/utils/ts/requests' import { revalidateTags } from '@services/utils/ts/requests'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useLHSession } from '@components/Contexts/LHSessionContext'
function CreateCourseModal({ closeModal, orgslug }: any) { function CreateCourseModal({ closeModal, orgslug }: any) {
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const session = useLHSession() as any;
const [name, setName] = React.useState('') const [name, setName] = React.useState('')
const [description, setDescription] = React.useState('') const [description, setDescription] = React.useState('')
const [learnings, setLearnings] = React.useState('') const [learnings, setLearnings] = React.useState('')
@ -53,7 +55,6 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
const handleVisibilityChange = (event: React.ChangeEvent<any>) => { const handleVisibilityChange = (event: React.ChangeEvent<any>) => {
setVisibility(event.target.value) setVisibility(event.target.value)
console.log(visibility)
} }
const handleTagsChange = (event: React.ChangeEvent<any>) => { const handleTagsChange = (event: React.ChangeEvent<any>) => {
@ -71,7 +72,8 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
let status = await createNewCourse( let status = await createNewCourse(
orgId, orgId,
{ name, description, tags, visibility }, { name, description, tags, visibility },
thumbnail thumbnail,
session.data?.tokens?.access_token
) )
await revalidateTags(['courses'], orgslug) await revalidateTags(['courses'], orgslug)
setIsSubmitting(false) setIsSubmitting(false)
@ -80,9 +82,6 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
closeModal() closeModal()
router.refresh() router.refresh()
await revalidateTags(['courses'], orgslug) await revalidateTags(['courses'], orgslug)
// refresh page (FIX for Next.js BUG)
// window.location.reload();
} else { } else {
alert('Error creating course, please see console logs') alert('Error creating course, please see console logs')
} }

View file

@ -1,10 +1,11 @@
'use client'; 'use client';
import { useCourse } from '@components/Contexts/CourseContext'; import { useCourse } from '@components/Contexts/CourseContext';
import { useLHSession } from '@components/Contexts/LHSessionContext';
import { useOrg } from '@components/Contexts/OrgContext'; import { useOrg } from '@components/Contexts/OrgContext';
import { getAPIUrl } from '@services/config/config'; import { getAPIUrl } from '@services/config/config';
import { linkResourcesToUserGroup } from '@services/usergroups/usergroups'; import { linkResourcesToUserGroup } from '@services/usergroups/usergroups';
import { swrFetcher } from '@services/utils/ts/requests'; import { swrFetcher } from '@services/utils/ts/requests';
import { AlertTriangle, Info } from 'lucide-react'; import { Info } from 'lucide-react';
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
@ -17,6 +18,8 @@ type LinkToUserGroupProps = {
function LinkToUserGroup(props: LinkToUserGroupProps) { function LinkToUserGroup(props: LinkToUserGroupProps) {
const course = useCourse() as any const course = useCourse() as any
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const courseStructure = course.courseStructure const courseStructure = course.courseStructure
const { data: usergroups } = useSWR( const { data: usergroups } = useSWR(
@ -27,8 +30,7 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
const handleLink = async () => { const handleLink = async () => {
console.log('selectedUserGroup', selectedUserGroup) const res = await linkResourcesToUserGroup(selectedUserGroup, courseStructure.course_uuid, access_token)
const res = await linkResourcesToUserGroup(selectedUserGroup, courseStructure.course_uuid)
if (res.status === 200) { if (res.status === 200) {
props.setUserGroupModal(false) props.setUserGroupModal(false)
toast.success('Successfully linked to usergroup') toast.success('Successfully linked to usergroup')
@ -37,7 +39,6 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
else { else {
toast.error('Error ' + res.status + ': ' + res.data.detail) toast.error('Error ' + res.status + ': ' + res.data.detail)
} }
} }
useEffect(() => { useEffect(() => {

View file

@ -2,7 +2,8 @@ import { useOrg } from '@components/Contexts/OrgContext'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { createInviteCode, createInviteCodeWithUserGroup } from '@services/organizations/invites' import { createInviteCode, createInviteCodeWithUserGroup } from '@services/organizations/invites'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Shield, Ticket } from 'lucide-react' import { Ticket } from 'lucide-react'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
@ -13,6 +14,8 @@ type OrgInviteCodeGenerateProps = {
function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) { function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [usergroup_id, setUsergroup_id] = React.useState(0); const [usergroup_id, setUsergroup_id] = React.useState(0);
const { data: usergroups } = useSWR( const { data: usergroups } = useSWR(
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null, org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
@ -20,7 +23,7 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
) )
async function createInviteWithUserGroup() { async function createInviteWithUserGroup() {
let res = await createInviteCodeWithUserGroup(org.id, usergroup_id) let res = await createInviteCodeWithUserGroup(org.id, usergroup_id, session.data?.tokens?.access_token)
if (res.status == 200) { if (res.status == 200) {
mutate(`${getAPIUrl()}orgs/${org.id}/invites`) mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
props.setInvitesModal(false) props.setInvitesModal(false)
@ -30,7 +33,7 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
} }
async function createInvite() { async function createInvite() {
let res = await createInviteCode(org.id) let res = await createInviteCode(org.id, session.data?.tokens?.access_token)
if (res.status == 200) { if (res.status == 200) {
mutate(`${getAPIUrl()}orgs/${org.id}/invites`) mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
props.setInvitesModal(false) props.setInvitesModal(false)

View file

@ -14,13 +14,16 @@ import { BarLoader } from 'react-spinners'
import { createUserGroup } from '@services/usergroups/usergroups' import { createUserGroup } from '@services/usergroups/usergroups'
import { mutate } from 'swr' import { mutate } from 'swr'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type AddUserGroupProps = { type AddUserGroupProps = {
setCreateUserGroupModal: any setCreateUserGroupModal: any
} }
function AddUserGroup(props: AddUserGroupProps) { function AddUserGroup(props: AddUserGroupProps) {
const org = useOrg() as any const org = useOrg() as any;
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const [userGroupName, setUserGroupName] = React.useState('') const [userGroupName, setUserGroupName] = React.useState('')
const [userGroupDescription, setUserGroupDescription] = React.useState('') const [userGroupDescription, setUserGroupDescription] = React.useState('')
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
@ -42,7 +45,7 @@ function AddUserGroup(props: AddUserGroupProps) {
description: userGroupDescription, description: userGroupDescription,
org_id: org.id org_id: org.id
} }
const res = await createUserGroup(obj) const res = await createUserGroup(obj, access_token)
if (res.status == 200) { if (res.status == 200) {
setIsSubmitting(false) setIsSubmitting(false)
mutate(`${getAPIUrl()}usergroups/org/${org.id}`) mutate(`${getAPIUrl()}usergroups/org/${org.id}`)

View file

@ -1,3 +1,4 @@
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { linkUserToUserGroup, unLinkUserToUserGroup } from '@services/usergroups/usergroups' import { linkUserToUserGroup, unLinkUserToUserGroup } from '@services/usergroups/usergroups'
@ -14,6 +15,8 @@ type ManageUsersProps = {
function ManageUsers(props: ManageUsersProps) { function ManageUsers(props: ManageUsersProps) {
const org = useOrg() as any const org = useOrg() as any
const session = useLHSession() as any
const access_token = session.data.tokens.access_token;
const { data: OrgUsers } = useSWR( const { data: OrgUsers } = useSWR(
org ? `${getAPIUrl()}orgs/${org.id}/users` : null, org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
swrFetcher swrFetcher
@ -31,7 +34,7 @@ function ManageUsers(props: ManageUsersProps) {
} }
const handleLinkUser = async (user_id: any) => { const handleLinkUser = async (user_id: any) => {
const res = await linkUserToUserGroup(props.usergroup_id, user_id) const res = await linkUserToUserGroup(props.usergroup_id, user_id,access_token)
if (res.status === 200) { if (res.status === 200) {
toast.success('User linked successfully') toast.success('User linked successfully')
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`) mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
@ -41,7 +44,7 @@ function ManageUsers(props: ManageUsersProps) {
} }
const handleUnlinkUser = async (user_id: any) => { const handleUnlinkUser = async (user_id: any) => {
const res = await unLinkUserToUserGroup(props.usergroup_id, user_id) const res = await unLinkUserToUserGroup(props.usergroup_id, user_id,access_token)
if (res.status === 200) { if (res.status === 200) {
toast.success('User unlinked successfully') toast.success('User unlinked successfully')
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`) mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)

Some files were not shown because too many files have changed in this diff Show more