mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #260 from learnhouse/feat/use-next-auth
New Auth Mechanism + Google OAuth
This commit is contained in:
commit
ddbd413539
140 changed files with 2833 additions and 1919 deletions
1
.npmrc
1
.npmrc
|
|
@ -1 +1,2 @@
|
|||
shared-workspace-lockfile=false
|
||||
package-manager-strict=false
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
from datetime import timedelta
|
||||
from typing import Literal, Optional
|
||||
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from pydantic import BaseModel, EmailStr
|
||||
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 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()
|
||||
|
|
@ -74,6 +77,58 @@ async def login(
|
|||
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")
|
||||
def logout(Authorize: AuthJWT = Depends()):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from src.services.orgs.invites import (
|
|||
get_invite_code,
|
||||
get_invite_codes,
|
||||
)
|
||||
from src.services.orgs.join import JoinOrg, join_org
|
||||
from src.services.orgs.users import (
|
||||
get_list_of_invited_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)
|
||||
|
||||
|
||||
@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}")
|
||||
async def api_update_user_role(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ async def api_create_user_with_orgid(
|
|||
"""
|
||||
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
|
||||
if (
|
||||
|
|
|
|||
70
apps/api/src/services/auth/utils.py
Normal file
70
apps/api/src/services/auth/utils.py
Normal 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)
|
||||
119
apps/api/src/services/orgs/join.py
Normal file
119
apps/api/src/services/orgs/join.py
Normal 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.",
|
||||
)
|
||||
|
|
@ -436,8 +436,7 @@ async def get_orgs_by_user(
|
|||
|
||||
orgs = result.all()
|
||||
|
||||
return orgs
|
||||
|
||||
return orgs #type:ignore
|
||||
|
||||
# Config related
|
||||
async def update_org_signup_mechanism(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def send_account_creation_email(
|
|||
<body>
|
||||
<p>Hello {user.username}</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>
|
||||
</html>
|
||||
""",
|
||||
|
|
|
|||
5
apps/collaboration/pnpm-lock.yaml
generated
5
apps/collaboration/pnpm-lock.yaml
generated
|
|
@ -10,7 +10,7 @@ importers:
|
|||
dependencies:
|
||||
'@hocuspocus/server':
|
||||
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:
|
||||
specifier: ^1.0.36
|
||||
version: 1.1.1
|
||||
|
|
@ -80,7 +80,6 @@ packages:
|
|||
|
||||
bun@1.1.1:
|
||||
resolution: {integrity: sha512-gV90TkJgHvI50X9BoKQ3zVpPEY6YP0vqOww2uZmsOyckZSRlcFYWhXZwFj6PV8KCFINYs8VZ65m59U2RuFYfWw==}
|
||||
cpu: [arm64, x64]
|
||||
os: [darwin, linux, win32]
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -133,7 +132,7 @@ snapshots:
|
|||
dependencies:
|
||||
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:
|
||||
'@hocuspocus/common': 2.11.3
|
||||
async-lock: 1.4.1
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
"react/no-unescaped-entities": "off",
|
||||
"@next/next/no-page-custom-font": "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"]
|
||||
}
|
||||
|
|
|
|||
2
apps/web/.gitignore
vendored
2
apps/web/.gitignore
vendored
|
|
@ -43,3 +43,5 @@ next.config.original.js
|
|||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
||||
certificates
|
||||
6
apps/web/app/api/auth/[...nextauth]/route.ts
Normal file
6
apps/web/app/api/auth/[...nextauth]/route.ts
Normal 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 }
|
||||
19
apps/web/app/auth/layout.tsx
Normal file
19
apps/web/app/auth/layout.tsx
Normal 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' />
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,12 @@ import * as Form from '@radix-ui/react-form'
|
|||
import { useFormik } from 'formik'
|
||||
import { getOrgLogoMediaDirectory } from '@services/media/media'
|
||||
import React from 'react'
|
||||
import { loginAndGetToken } from '@services/auth/auth'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import { AlertTriangle, UserRoundPlus } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
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 {
|
||||
org: any
|
||||
|
|
@ -40,7 +41,9 @@ const validate = (values: any) => {
|
|||
|
||||
const LoginClient = (props: LoginClientProps) => {
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const session = useLHSession() as any;
|
||||
|
||||
const [error, setError] = React.useState('')
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
|
|
@ -50,25 +53,25 @@ const LoginClient = (props: LoginClientProps) => {
|
|||
validate,
|
||||
onSubmit: async (values) => {
|
||||
setIsSubmitting(true)
|
||||
let res = await loginAndGetToken(values.email, values.password)
|
||||
let message = await res.json()
|
||||
if (res.status == 200) {
|
||||
router.push(`/`)
|
||||
setIsSubmitting(false)
|
||||
} else if (
|
||||
res.status == 401 ||
|
||||
res.status == 400 ||
|
||||
res.status == 404 ||
|
||||
res.status == 409
|
||||
) {
|
||||
setError(message.detail)
|
||||
setIsSubmitting(false)
|
||||
const res = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
callbackUrl: '/redirect_from_auth'
|
||||
});
|
||||
if (res && res.error) {
|
||||
setError("Wrong Email or password");
|
||||
setIsSubmitting(false);
|
||||
} else {
|
||||
setError('Something went wrong')
|
||||
setIsSubmitting(false)
|
||||
await signIn('credentials', {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
callbackUrl: '/redirect_from_auth'
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="grid grid-flow-col justify-stretch h-screen">
|
||||
<div
|
||||
|
|
@ -165,7 +168,6 @@ const LoginClient = (props: LoginClientProps) => {
|
|||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex py-4">
|
||||
<Form.Submit asChild>
|
||||
<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>
|
||||
</div>
|
||||
</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>
|
||||
|
|
@ -3,14 +3,14 @@ import LoginClient from './login'
|
|||
import { Metadata } from 'next'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string; courseid: string }
|
||||
params: { orgslug: string }
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const orgslug = params.orgslug
|
||||
export async function generateMetadata(params: MetadataProps): Promise<Metadata> {
|
||||
const orgslug = params.searchParams.orgslug
|
||||
|
||||
//const orgslug = params.orgslug
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 0,
|
||||
|
|
@ -22,8 +22,8 @@ export async function generateMetadata({
|
|||
}
|
||||
}
|
||||
|
||||
const Login = async (params: any) => {
|
||||
const orgslug = params.params.orgslug
|
||||
const Login = async (params: MetadataProps) => {
|
||||
const orgslug = params.searchParams.orgslug
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 0,
|
||||
tags: ['organizations'],
|
||||
114
apps/web/app/auth/options.ts
Normal file
114
apps/web/app/auth/options.ts
Normal 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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import { getUriWithOrg } from '@services/config/config'
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useFormik } from 'formik'
|
||||
import { resetPassword, sendResetLink } from '@services/auth/auth'
|
||||
import { resetPassword } from '@services/auth/auth'
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
|
|||
import Link from 'next/link'
|
||||
import { signUpWithInviteCode } from '@services/auth/auth'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { signIn } from 'next-auth/react'
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -92,7 +93,7 @@ function InviteOnlySignUpComponent(props: InviteOnlySignUpProps) {
|
|||
},
|
||||
})
|
||||
|
||||
useEffect(() => {}, [org])
|
||||
useEffect(() => { }, [org])
|
||||
|
||||
return (
|
||||
<div className="login-form m-auto w-72">
|
||||
|
|
@ -180,6 +181,13 @@ function InviteOnlySignUpComponent(props: InviteOnlySignUpProps) {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
|
|||
import Link from 'next/link'
|
||||
import { signup } from '@services/auth/auth'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { signIn } from 'next-auth/react'
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -88,7 +89,7 @@ function OpenSignUpComponent() {
|
|||
},
|
||||
})
|
||||
|
||||
useEffect(() => {}, [org])
|
||||
useEffect(() => { }, [org])
|
||||
|
||||
return (
|
||||
<div className="login-form m-auto w-72">
|
||||
|
|
@ -176,6 +177,13 @@ function OpenSignUpComponent() {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@ type MetadataProps = {
|
|||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const orgslug = params.orgslug
|
||||
export async function generateMetadata(
|
||||
params
|
||||
: MetadataProps): Promise<Metadata> {
|
||||
const orgslug = params.searchParams.orgslug
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 0,
|
||||
|
|
@ -25,7 +25,7 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const SignUp = async (params: any) => {
|
||||
const orgslug = params.params.orgslug
|
||||
const orgslug = params.searchParams.orgslug
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 0,
|
||||
tags: ['organizations'],
|
||||
|
|
@ -4,9 +4,9 @@ import Image from 'next/image'
|
|||
import { getOrgLogoMediaDirectory } from '@services/media/media'
|
||||
import Link from 'next/link'
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
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 UserAvatar from '@components/Objects/UserAvatar'
|
||||
import OpenSignUpComponent from './OpenSignup'
|
||||
|
|
@ -16,13 +16,15 @@ import { validateInviteCode } from '@services/organizations/invites'
|
|||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import Toast from '@components/StyledElements/Toast/Toast'
|
||||
import toast from 'react-hot-toast'
|
||||
import { BarLoader } from 'react-spinners'
|
||||
import { joinOrg } from '@services/organizations/orgs'
|
||||
|
||||
interface SignUpClientProps {
|
||||
org: any
|
||||
}
|
||||
|
||||
function SignUpClient(props: SignUpClientProps) {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const [joinMethod, setJoinMethod] = React.useState('open')
|
||||
const [inviteCode, setInviteCode] = React.useState('')
|
||||
const searchParams = useSearchParams()
|
||||
|
|
@ -33,9 +35,6 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
setJoinMethod(
|
||||
props.org?.config?.config?.GeneralConfig.users.signup_mechanism
|
||||
)
|
||||
console.log(
|
||||
props.org?.config?.config?.GeneralConfig.users.signup_mechanism
|
||||
)
|
||||
}
|
||||
if (inviteCodeParam) {
|
||||
setInviteCode(inviteCodeParam)
|
||||
|
|
@ -72,7 +71,7 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
props.org.org_uuid,
|
||||
props.org?.logo_image
|
||||
)}`}
|
||||
alt="Learnhouse"
|
||||
alt="LearnHouse"
|
||||
style={{ width: 'auto', height: 70 }}
|
||||
className="rounded-xl shadow-xl inset-0 ring-1 ring-inset ring-black/10 bg-white"
|
||||
/>
|
||||
|
|
@ -92,15 +91,15 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
</div>
|
||||
<div className="left-join-part bg-white flex flex-row">
|
||||
{joinMethod == 'open' &&
|
||||
(session.isAuthenticated ? (
|
||||
(session.status == 'authenticated' ? (
|
||||
<LoggedInJoinScreen inviteCode={inviteCode} />
|
||||
) : (
|
||||
<OpenSignUpComponent />
|
||||
))}
|
||||
{joinMethod == 'inviteOnly' &&
|
||||
(inviteCode ? (
|
||||
session.isAuthenticated ? (
|
||||
<LoggedInJoinScreen />
|
||||
session.status == 'authenticated' ? (
|
||||
<LoggedInJoinScreen inviteCode={inviteCode} />
|
||||
) : (
|
||||
<InviteOnlySignUpComponent inviteCode={inviteCode} />
|
||||
)
|
||||
|
|
@ -113,9 +112,32 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
}
|
||||
|
||||
const LoggedInJoinScreen = (props: any) => {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const org = useOrg() as any
|
||||
const invite_code = props.inviteCode
|
||||
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(() => {
|
||||
if (session && org) {
|
||||
|
|
@ -125,18 +147,23 @@ const LoggedInJoinScreen = (props: any) => {
|
|||
|
||||
return (
|
||||
<div className="flex flex-row items-center mx-auto">
|
||||
<Toast />
|
||||
<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">
|
||||
<span className="items-center">Hi</span>
|
||||
<span className="capitalize flex space-x-2 items-center">
|
||||
<UserAvatar rounded="rounded-xl" border="border-4" width={35} />
|
||||
<span>{session.user.username},</span>
|
||||
<span>{session.data.username},</span>
|
||||
</span>
|
||||
<span>join {org?.name} ?</span>
|
||||
</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">
|
||||
<UserPlus size={18} />
|
||||
<p>Join </p>
|
||||
<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">
|
||||
{isSumbitting ? <BarLoader
|
||||
cssOverride={{ borderRadius: 60 }}
|
||||
width={60}
|
||||
color="#ffffff"
|
||||
/> : <><UserPlus size={18} />
|
||||
<p>Join </p></>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -144,7 +171,7 @@ const LoggedInJoinScreen = (props: any) => {
|
|||
}
|
||||
|
||||
const NoTokenScreen = (props: any) => {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const org = useOrg() as any
|
||||
const router = useRouter()
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
|
|
@ -157,14 +184,14 @@ const NoTokenScreen = (props: any) => {
|
|||
|
||||
const validateCode = async () => {
|
||||
setIsLoading(true)
|
||||
let res = await validateInviteCode(org?.id, inviteCode)
|
||||
let res = await validateInviteCode(org?.id, inviteCode, session?.user?.tokens.access_token)
|
||||
//wait for 1s
|
||||
if (res.success) {
|
||||
toast.success(
|
||||
"Invite code is valid, you'll be redirected to the signup page in a few seconds"
|
||||
)
|
||||
setTimeout(() => {
|
||||
router.push(`/signup?inviteCode=${inviteCode}`)
|
||||
router.push(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`)
|
||||
}, 2000)
|
||||
} else {
|
||||
toast.error('Invite code is invalid')
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import { default as React } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getCourseMetadata } from '@services/courses/courses'
|
||||
import { Metadata } from 'next'
|
||||
import { getActivityWithAuthHeader } from '@services/courses/activities'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { getOrganizationContextInfoWithId } from '@services/organizations/orgs'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext'
|
||||
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 })
|
||||
|
||||
|
||||
|
|
@ -20,10 +19,10 @@ type MetadataProps = {
|
|||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
// Get Org context information
|
||||
const course_meta = await getCourseMetadataWithAuthHeader(
|
||||
const course_meta = await getCourseMetadata(
|
||||
params.courseid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
@ -36,11 +35,11 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const EditActivity = async (params: any) => {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const activityuuid = params.params.activityuuid
|
||||
const courseid = params.params.courseid
|
||||
const courseInfo = await getCourseMetadataWithAuthHeader(
|
||||
const courseInfo = await getCourseMetadata(
|
||||
courseid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
@ -53,19 +52,17 @@ const EditActivity = async (params: any) => {
|
|||
const org = await getOrganizationContextInfoWithId(courseInfo.org_id, {
|
||||
revalidate: 180,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
}, access_token)
|
||||
|
||||
return (
|
||||
<EditorOptionsProvider options={{ isEditable: true }}>
|
||||
<AIEditorProvider>
|
||||
<SessionProvider>
|
||||
<EditorWrapper
|
||||
org={org}
|
||||
course={courseInfo}
|
||||
activity={activity}
|
||||
content={activity.content}
|
||||
></EditorWrapper>
|
||||
</SessionProvider>
|
||||
<EditorWrapper
|
||||
org={org}
|
||||
course={courseInfo}
|
||||
activity={activity}
|
||||
content={activity.content}
|
||||
></EditorWrapper>
|
||||
</AIEditorProvider>
|
||||
</EditorOptionsProvider>
|
||||
)
|
||||
|
|
|
|||
57
apps/web/app/home/home.tsx
Normal file
57
apps/web/app/home/home.tsx
Normal 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
|
||||
16
apps/web/app/home/page.tsx
Normal file
16
apps/web/app/home/page.tsx
Normal 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
|
||||
|
|
@ -10,6 +10,7 @@ import { getAPIUrl } from '@services/config/config'
|
|||
import { createNewUserInstall, updateInstall } from '@services/install/install'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useFormik } from 'formik'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React from 'react'
|
||||
import { BarLoader } from 'react-spinners'
|
||||
|
|
@ -47,11 +48,13 @@ const validate = (values: any) => {
|
|||
|
||||
function AccountCreation() {
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const router = useRouter()
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { getAPIUrl } from '@services/config/config'
|
||||
import { createDefaultElements, updateInstall } from '@services/install/install'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
function DefaultElements() {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const [isSubmitted, setIsSubmitted] = React.useState(false)
|
||||
const router = useRouter()
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@ import { getAPIUrl } from '@services/config/config'
|
|||
import { updateInstall } from '@services/install/install'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { Check } from 'lucide-react'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
const Finish = () => {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const router = useRouter()
|
||||
|
||||
async function finishInstall() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useEffect } from 'react'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
|
||||
function GetStarted() {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const router = useRouter()
|
||||
|
||||
async function startInstallation() {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import useSWR from 'swr'
|
|||
import { createNewOrgInstall, updateInstall } from '@services/install/install'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Check } from 'lucide-react'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -40,11 +41,13 @@ const validate = (values: any) => {
|
|||
}
|
||||
|
||||
function OrgCreation() {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const [isSubmitted, setIsSubmitted] = React.useState(false)
|
||||
const router = useRouter()
|
||||
|
|
|
|||
|
|
@ -4,16 +4,19 @@ import {
|
|||
updateInstall,
|
||||
} from '@services/install/install'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
function SampleData() {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const {
|
||||
data: install,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, swrFetcher)
|
||||
} = useSWR(`${getAPIUrl()}install/latest`, (url) => swrFetcher(url, access_token))
|
||||
const router = useRouter()
|
||||
|
||||
function createSampleData() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
'use client'
|
||||
import '../styles/globals.css'
|
||||
import StyledComponentsRegistry from '../components/Utils/libs/styled-registry'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
import LHSessionProvider from '@components/Contexts/LHSessionContext'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
@ -18,18 +19,22 @@ export default function RootLayout({
|
|||
<html className="" lang="en">
|
||||
<head />
|
||||
<body>
|
||||
<StyledComponentsRegistry>
|
||||
<motion.main
|
||||
variants={variants} // Pass the variant object into Framer Motion
|
||||
initial="hidden" // Set the initial state to variants.hidden
|
||||
animate="enter" // Animated state to variants.enter
|
||||
exit="exit" // Exit state (used later) to variants.exit
|
||||
transition={{ type: 'linear' }} // Set the transition to linear
|
||||
className=""
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
</StyledComponentsRegistry>
|
||||
<SessionProvider>
|
||||
<LHSessionProvider>
|
||||
<StyledComponentsRegistry>
|
||||
<motion.main
|
||||
variants={variants} // Pass the variant object into Framer Motion
|
||||
initial="hidden" // Set the initial state to variants.hidden
|
||||
animate="enter" // Animated state to variants.enter
|
||||
exit="exit" // Exit state (used later) to variants.exit
|
||||
transition={{ type: 'linear' }} // Set the transition to linear
|
||||
className=""
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
</StyledComponentsRegistry>
|
||||
</LHSessionProvider>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
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 { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { Metadata } from 'next'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import Link from 'next/link'
|
||||
|
||||
type MetadataProps = {
|
||||
|
|
@ -16,15 +16,15 @@ type MetadataProps = {
|
|||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const col = await getCollectionByIdWithAuthHeader(
|
||||
const col = await getCollectionById(
|
||||
params.collectionid,
|
||||
access_token ? access_token : null,
|
||||
{ revalidate: 0, tags: ['collections'] }
|
||||
|
|
@ -53,14 +53,14 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const CollectionPage = async (params: any) => {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const org = await getOrganizationContextInfo(params.params.orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const orgslug = params.params.orgslug
|
||||
const col = await getCollectionByIdWithAuthHeader(
|
||||
const col = await getCollectionById(
|
||||
params.params.collectionid,
|
||||
access_token ? access_token : null,
|
||||
{ revalidate: 0, tags: ['collections'] }
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ import useSWR from 'swr'
|
|||
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
||||
import { revalidateTags, swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function NewCollection(params: 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 [name, setName] = React.useState('')
|
||||
const [description, setDescription] = React.useState('')
|
||||
|
|
@ -16,7 +19,7 @@ function NewCollection(params: any) {
|
|||
const router = useRouter()
|
||||
const { data: courses, error: error } = useSWR(
|
||||
`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const [isPublic, setIsPublic] = useState('true')
|
||||
|
||||
|
|
@ -44,7 +47,7 @@ function NewCollection(params: any) {
|
|||
public: isPublic,
|
||||
org_id: org.id,
|
||||
}
|
||||
await createCollection(collection)
|
||||
await createCollection(collection, session.data?.tokens?.access_token)
|
||||
await revalidateTags(['collections'], org.slug)
|
||||
// reload the page
|
||||
router.refresh()
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ import AuthenticatedClientElement from '@components/Security/AuthenticatedClient
|
|||
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'
|
||||
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { getOrgCollectionsWithAuthHeader } from '@services/courses/collections'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { Metadata } from 'next'
|
||||
import { cookies } from 'next/headers'
|
||||
import Link from 'next/link'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'
|
||||
import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton'
|
||||
import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { getOrgCollections } from '@services/courses/collections'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string; courseid: string }
|
||||
|
|
@ -49,15 +49,15 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const CollectionsPage = async (params: any) => {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const orgslug = params.params.orgslug
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const org_id = org.id
|
||||
const collections = await getOrgCollectionsWithAuthHeader(
|
||||
const collections = await getOrgCollections(
|
||||
org_id,
|
||||
access_token ? access_token : null,
|
||||
{ revalidate: 0, tags: ['collections'] }
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { useOrg } from '@components/Contexts/OrgContext'
|
|||
import { CourseProvider } from '@components/Contexts/CourseContext'
|
||||
import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk'
|
||||
import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
interface ActivityClientProps {
|
||||
activityid: string
|
||||
|
|
@ -106,11 +107,10 @@ function ActivityClient(props: ActivityClientProps) {
|
|||
|
||||
{activity ? (
|
||||
<div
|
||||
className={`p-7 pt-4 drop-shadow-sm rounded-lg ${
|
||||
activity.activity_type == 'TYPE_DYNAMIC'
|
||||
className={`p-7 pt-4 drop-shadow-sm rounded-lg ${activity.activity_type == 'TYPE_DYNAMIC'
|
||||
? 'bg-white'
|
||||
: 'bg-zinc-950'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
{activity.activity_type == 'TYPE_DYNAMIC' && (
|
||||
|
|
@ -147,13 +147,14 @@ export function MarkStatus(props: {
|
|||
orgslug: string
|
||||
}) {
|
||||
const router = useRouter()
|
||||
console.log(props.course.trail)
|
||||
const session = useLHSession() as any;
|
||||
|
||||
async function markActivityAsCompleteFront() {
|
||||
const trail = await markActivityAsComplete(
|
||||
props.orgslug,
|
||||
props.course.course_uuid,
|
||||
'activity_' + props.activityid
|
||||
'activity_' + props.activityid,
|
||||
session.data?.tokens?.access_token
|
||||
)
|
||||
router.refresh()
|
||||
}
|
||||
|
|
@ -169,8 +170,6 @@ export function MarkStatus(props: {
|
|||
}
|
||||
}
|
||||
|
||||
console.log('isActivityCompleted', isActivityCompleted())
|
||||
|
||||
return (
|
||||
<>
|
||||
{isActivityCompleted() ? (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { getActivityWithAuthHeader } from '@services/courses/activities'
|
||||
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getCourseMetadata } from '@services/courses/courses'
|
||||
import ActivityClient from './activity'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { Metadata } from 'next'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string; courseuuid: string; activityid: string }
|
||||
|
|
@ -14,15 +14,15 @@ type MetadataProps = {
|
|||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const course_meta = await getCourseMetadataWithAuthHeader(
|
||||
const course_meta = await getCourseMetadata(
|
||||
params.courseuuid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
@ -58,13 +58,13 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const ActivityPage = async (params: any) => {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const activityid = params.params.activityid
|
||||
const courseuuid = params.params.courseuuid
|
||||
const orgslug = params.params.orgslug
|
||||
|
||||
const course_meta = await getCourseMetadataWithAuthHeader(
|
||||
const course_meta = await getCourseMetadata(
|
||||
courseuuid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ import { useOrg } from '@components/Contexts/OrgContext'
|
|||
import UserAvatar from '@components/Objects/UserAvatar'
|
||||
import CourseUpdates from '@components/Objects/CourseUpdates/CourseUpdates'
|
||||
import { CourseProvider } from '@components/Contexts/CourseContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
const CourseClient = (props: any) => {
|
||||
const [user, setUser] = useState<any>({})
|
||||
const [learnings, setLearnings] = useState<any>([])
|
||||
const session = useLHSession() as any;
|
||||
const courseuuid = props.courseuuid
|
||||
const orgslug = props.orgslug
|
||||
const course = props.course
|
||||
|
|
@ -35,7 +37,7 @@ const CourseClient = (props: any) => {
|
|||
|
||||
async function startCourseUI() {
|
||||
// Create activity
|
||||
await startCourse('course_' + courseuuid, orgslug)
|
||||
await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
router.refresh()
|
||||
|
||||
|
|
@ -54,7 +56,7 @@ const CourseClient = (props: any) => {
|
|||
|
||||
async function quitCourse() {
|
||||
// Close activity
|
||||
let activity = await removeCourse('course_' + courseuuid, orgslug)
|
||||
let activity = await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
|
||||
// Mutate course
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
router.refresh()
|
||||
|
|
@ -277,7 +279,7 @@ const CourseClient = (props: any) => {
|
|||
<UserAvatar
|
||||
border="border-8"
|
||||
avatar_url={course.authors[0].avatar_image ? getUserAvatarMediaDirectory(course.authors[0].user_uuid, course.authors[0].avatar_image) : ''}
|
||||
predefined_avatar={course.authors[0].avatar_image ? undefined : 'empty'}
|
||||
predefined_avatar={course.authors[0].avatar_image ? undefined : 'empty'}
|
||||
width={100}
|
||||
/>
|
||||
<div className="-space-y-2 ">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
'use client' // Error components must be Client Components
|
||||
|
||||
import ErrorUI from '@components/StyledElements/Error/Error'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Error({
|
||||
|
|
@ -17,7 +16,7 @@ export default function Error({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ErrorUI></ErrorUI>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
|
||||
export default function Loading() {
|
||||
// Or a custom loading skeleton component
|
||||
return <PageLoading></PageLoading>
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react'
|
||||
import CourseClient from './course'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'
|
||||
import { getCourseMetadata } from '@services/courses/courses'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { Metadata } from 'next'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { getServerSession } from 'next-auth'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string; courseuuid: string }
|
||||
|
|
@ -15,15 +15,15 @@ type MetadataProps = {
|
|||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const course_meta = await getCourseMetadataWithAuthHeader(
|
||||
const course_meta = await getCourseMetadata(
|
||||
params.courseuuid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
@ -67,11 +67,11 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const CoursePage = async (params: any) => {
|
||||
const cookieStore = cookies()
|
||||
const courseuuid = params.params.courseuuid
|
||||
const orgslug = params.params.orgslug
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const course_meta = await getCourseMetadataWithAuthHeader(
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const course_meta = await getCourseMetadata(
|
||||
courseuuid,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react'
|
||||
import Courses from './courses'
|
||||
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses'
|
||||
import { Metadata } from 'next'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { getOrgCourses } from '@services/courses/courses'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string }
|
||||
|
|
@ -49,9 +49,10 @@ const CoursesPage = async (params: any) => {
|
|||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const courses = await getOrgCoursesWithAuthHeader(
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
|
||||
const courses = await getOrgCourses(
|
||||
orgslug,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
import '@styles/globals.css'
|
||||
import { Menu } from '@components/Objects/Menu/Menu'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
export const dynamic = 'force-dynamic'
|
||||
import { Metadata } from 'next'
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses'
|
||||
import { getOrgCourses } from '@services/courses/courses'
|
||||
import Link from 'next/link'
|
||||
import { getOrgCollectionsWithAuthHeader } from '@services/courses/collections'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { cookies } from 'next/headers'
|
||||
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
|
||||
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail'
|
||||
import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'
|
||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
|
||||
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'
|
||||
import NewCollectionButton from '@components/StyledElements/Buttons/NewCollectionButton'
|
||||
import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
|
||||
import { getOrgCollections } from '@services/courses/collections'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string }
|
||||
|
|
@ -54,10 +54,9 @@ export async function generateMetadata({
|
|||
|
||||
const OrgHomePage = async (params: any) => {
|
||||
const orgslug = params.params.orgslug
|
||||
const cookieStore = cookies()
|
||||
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const courses = await getOrgCoursesWithAuthHeader(
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const courses = await getOrgCourses(
|
||||
orgslug,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
@ -67,7 +66,7 @@ const OrgHomePage = async (params: any) => {
|
|||
tags: ['organizations'],
|
||||
})
|
||||
const org_id = org.id
|
||||
const collections = await getOrgCollectionsWithAuthHeader(
|
||||
const collections = await getOrgCollections(
|
||||
org.id,
|
||||
access_token ? access_token : null,
|
||||
{ revalidate: 0, tags: ['courses'] }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import React from 'react'
|
|||
import { Metadata } from 'next'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import Trail from './trail'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string }
|
||||
|
|
@ -11,11 +13,13 @@ type MetadataProps = {
|
|||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, {
|
||||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
}, access_token)
|
||||
return {
|
||||
title: 'Trail — ' + org.name,
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
'use client'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import TrailCourseElement from '@components/Pages/Trail/TrailCourseElement'
|
||||
|
|
@ -11,14 +12,16 @@ import useSWR from 'swr'
|
|||
|
||||
function Trail(params: any) {
|
||||
let orgslug = params.orgslug
|
||||
const session = useLHSession() as any
|
||||
const access_token = session.data.tokens.access_token;
|
||||
const org = useOrg() as any
|
||||
const orgID = org?.id
|
||||
const { data: trail, error: error } = useSWR(
|
||||
`${getAPIUrl()}trail/org/${orgID}/trail`,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
|
||||
useEffect(() => {}, [trail, org])
|
||||
useEffect(() => { }, [trail, org])
|
||||
|
||||
return (
|
||||
<GeneralWrapperStyled>
|
||||
|
|
|
|||
26
apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx
Normal file
26
apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx
Normal 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
|
||||
|
|
@ -30,7 +30,6 @@ function CoursesHome(params: CourseProps) {
|
|||
<div>
|
||||
<div className="pl-10 mr-10 tracking-tighter">
|
||||
<BreadCrumbs type="courses" />
|
||||
|
||||
<div className="w-100 flex justify-between">
|
||||
<div className="pt-3 flex font-bold text-4xl">Courses</div>
|
||||
<AuthenticatedClientElement
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Link from 'next/link'
|
|||
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop'
|
||||
import { motion } from 'framer-motion'
|
||||
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'
|
||||
|
||||
export type CourseOverviewParams = {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses'
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||
import { Metadata } from 'next'
|
||||
import { cookies } from 'next/headers'
|
||||
import React from 'react'
|
||||
import CoursesHome from './client'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { getOrgCourses } from '@services/courses/courses'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string }
|
||||
|
|
@ -49,9 +49,9 @@ async function CoursesPage(params: any) {
|
|||
revalidate: 1800,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const cookieStore = cookies()
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const courses = await getOrgCoursesWithAuthHeader(
|
||||
const session = await getServerSession(nextAuthOptions)
|
||||
const access_token = session?.tokens?.access_token
|
||||
const courses = await getOrgCourses(
|
||||
orgslug,
|
||||
{ revalidate: 0, tags: ['courses'] },
|
||||
access_token ? access_token : null
|
||||
|
|
|
|||
|
|
@ -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 React from 'react'
|
||||
import ClientAdminLayout from './ClientAdminLayout'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'LearnHouse Dashboard',
|
||||
|
|
@ -17,14 +15,10 @@ function DashboardLayout({
|
|||
}) {
|
||||
return (
|
||||
<>
|
||||
<SessionProvider>
|
||||
<AdminAuthorization authorizationMode="page">
|
||||
<div className="flex">
|
||||
<LeftMenu />
|
||||
<div className="flex w-full">{children}</div>
|
||||
</div>
|
||||
</AdminAuthorization>
|
||||
</SessionProvider>
|
||||
<ClientAdminLayout
|
||||
params={params}>
|
||||
{children}
|
||||
</ClientAdminLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Link from 'next/link'
|
|||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { Info, Lock } from 'lucide-react'
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
export type SettingsParams = {
|
||||
subpage: string
|
||||
|
|
@ -15,7 +15,7 @@ export type SettingsParams = {
|
|||
}
|
||||
|
||||
function SettingsPage({ params }: { params: SettingsParams }) {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
|
||||
useEffect(() => {}, [session])
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import Link from 'next/link'
|
|||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react'
|
||||
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 OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers'
|
||||
import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess'
|
||||
|
|
@ -18,7 +18,7 @@ export type SettingsParams = {
|
|||
}
|
||||
|
||||
function UsersSettingsPage({ params }: { params: SettingsParams }) {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const org = useOrg() as any
|
||||
const [H1Label, setH1Label] = React.useState('')
|
||||
const [H2Label, setH2Label] = React.useState('')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
'use client'
|
||||
import { OrgProvider } from '@components/Contexts/OrgContext'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import Toast from '@components/StyledElements/Toast/Toast'
|
||||
import '@styles/globals.css'
|
||||
|
||||
|
|
@ -13,9 +12,9 @@ export default function RootLayout({
|
|||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Toast />
|
||||
<OrgProvider orgslug={params.orgslug}>
|
||||
<SessionProvider>{children}</SessionProvider>
|
||||
<Toast />
|
||||
{children}
|
||||
</OrgProvider>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,17 +13,14 @@ function useGetAIFeatures(props: UseGetAIFeatures) {
|
|||
const config = org?.config?.config?.AIConfig
|
||||
|
||||
if (!config) {
|
||||
console.log('AI or Organization config is not defined.')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!config.enabled) {
|
||||
console.log('AI is not enabled for this Organization.')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!config.features[feature]) {
|
||||
console.log(`Feature ${feature} is not enabled for this Organization.`)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import useAdminStatus from './Hooks/useAdminStatus'
|
|||
function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) {
|
||||
const isUserAdmin = useAdminStatus() as any
|
||||
return (
|
||||
<div>{isUserAdmin ? text : 'No content yet'}</div>
|
||||
<span>{isUserAdmin ? text : 'No content yet'}</span>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
'use client'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import React, { createContext, useContext, useEffect, useReducer } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
export const CourseContext = createContext(null) as any
|
||||
export const CourseDispatchContext = createContext(null) as any
|
||||
|
|
@ -15,9 +15,11 @@ export function CourseProvider({
|
|||
children: React.ReactNode
|
||||
courseuuid: string
|
||||
}) {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const { data: courseStructureData } = useSWR(
|
||||
`${getAPIUrl()}courses/${courseuuid}/meta`,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const [courseStructure, dispatchCourseStructure] = useReducer(courseReducer, {
|
||||
courseStructure: courseStructureData ? courseStructureData : {},
|
||||
|
|
@ -33,9 +35,9 @@ export function CourseProvider({
|
|||
payload: courseStructureData,
|
||||
})
|
||||
}
|
||||
}, [courseStructureData])
|
||||
}, [courseStructureData,session])
|
||||
|
||||
if (!courseStructureData) return <PageLoading></PageLoading>
|
||||
if (!courseStructureData) return
|
||||
|
||||
return (
|
||||
<CourseContext.Provider value={courseStructure}>
|
||||
|
|
|
|||
32
apps/web/components/Contexts/LHSessionContext.tsx
Normal file
32
apps/web/components/Contexts/LHSessionContext.tsx
Normal 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
|
||||
|
|
@ -1,32 +1,46 @@
|
|||
'use client'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { getAPIUrl, getUriWithoutOrg } from '@services/config/config'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import React, { createContext, useContext, useMemo } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { createContext } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import ErrorUI from '@components/StyledElements/Error/Error'
|
||||
import InfoUI from '@components/StyledElements/Info/Info'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export const OrgContext = createContext({}) as any
|
||||
export const OrgContext = createContext(null)
|
||||
|
||||
export function OrgProvider({
|
||||
children,
|
||||
orgslug,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
orgslug: string
|
||||
}) {
|
||||
const { data: org } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher)
|
||||
const router = useRouter()
|
||||
// Check if Org is Active
|
||||
const verifyIfOrgIsActive = () => {
|
||||
if (org && org?.config.config.GeneralConfig.active === false) {
|
||||
router.push('/404')
|
||||
}
|
||||
export function OrgProvider({ children, orgslug }: { children: React.ReactNode, orgslug: string }) {
|
||||
const session = useLHSession() as any
|
||||
const pathname = usePathname()
|
||||
const accessToken = session?.data?.tokens?.access_token
|
||||
const isAllowedPathname = ['/login', '/signup'].includes(pathname);
|
||||
|
||||
const { data: org, error: orgError } = useSWR(
|
||||
`${getAPIUrl()}orgs/slug/${orgslug}`,
|
||||
(url) => swrFetcher(url, accessToken)
|
||||
)
|
||||
const { data: orgs, error: orgsError } = useSWR(
|
||||
`${getAPIUrl()}orgs/user/page/1/limit/10`,
|
||||
(url) => swrFetcher(url, accessToken)
|
||||
)
|
||||
|
||||
|
||||
const isOrgActive = useMemo(() => org?.config?.config?.GeneralConfig?.active !== false, [org])
|
||||
const isUserPartOfTheOrg = useMemo(() => orgs?.some((userOrg: any) => userOrg.id === org?.id), [orgs, org?.id])
|
||||
|
||||
if (orgError || orgsError) return <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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -5,7 +5,8 @@ import Modal from '@components/StyledElements/Modal/Modal'
|
|||
import { getAPIUrl } from '@services/config/config'
|
||||
import { unLinkResourcesToUserGroup } from '@services/usergroups/usergroups'
|
||||
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 toast from 'react-hot-toast'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
|
|
@ -17,13 +18,15 @@ type EditCourseAccessProps = {
|
|||
|
||||
function EditCourseAccess(props: EditCourseAccessProps) {
|
||||
const [error, setError] = React.useState('')
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
|
||||
const course = useCourse() as any
|
||||
const dispatchCourse = useCourseDispatch() as any
|
||||
const courseStructure = course.courseStructure
|
||||
const { data: usergroups } = useSWR(
|
||||
courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const [isPublic, setIsPublic] = React.useState(courseStructure.public)
|
||||
|
||||
|
|
@ -109,7 +112,7 @@ function EditCourseAccess(props: EditCourseAccessProps) {
|
|||
status="info"
|
||||
></ConfirmationModal>
|
||||
</div>
|
||||
{!isPublic ? ( <UserGroupsSection usergroups={usergroups} />) : null}
|
||||
{!isPublic ? (<UserGroupsSection usergroups={usergroups} />) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -119,9 +122,11 @@ function EditCourseAccess(props: EditCourseAccessProps) {
|
|||
function UserGroupsSection({ usergroups }: { usergroups: any[] }) {
|
||||
const course = useCourse() as any
|
||||
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 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) {
|
||||
toast.success('Successfully unliked from usergroup')
|
||||
mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import FormLayout, {
|
|||
} from '@components/StyledElements/Form/Form'
|
||||
import { useFormik } from 'formik'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import * as Switch from '@radix-ui/react-switch'
|
||||
import * as Form from '@radix-ui/react-form'
|
||||
import React from 'react'
|
||||
import { useCourse, useCourseDispatch } from '../../../Contexts/CourseContext'
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import { getAPIUrl } from '@services/config/config'
|
|||
import { updateCourseThumbnail } from '@services/courses/courses'
|
||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
|
||||
import { ArrowBigUpDash, UploadCloud } from 'lucide-react'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import React from 'react'
|
||||
import { mutate } from 'swr'
|
||||
|
||||
function ThumbnailUpdate() {
|
||||
const course = useCourse() as any
|
||||
const session = useLHSession() as any;
|
||||
const org = useOrg() as any
|
||||
const [localThumbnail, setLocalThumbnail] = React.useState(null) as any
|
||||
const [isLoading, setIsLoading] = React.useState(false) as any
|
||||
|
|
@ -20,7 +22,8 @@ function ThumbnailUpdate() {
|
|||
setIsLoading(true)
|
||||
const res = await updateCourseThumbnail(
|
||||
course.courseStructure.course_uuid,
|
||||
file
|
||||
file,
|
||||
session.data?.tokens?.access_token
|
||||
)
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
// wait for 1 second to show loading animation
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs'
|
||||
import { revalidateTags } from '@services/utils/ts/requests'
|
||||
import { Layers } from 'lucide-react'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useEffect } from 'react'
|
||||
import { mutate } from 'swr'
|
||||
|
|
@ -23,6 +24,8 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
const [newActivityModal, setNewActivityModal] = React.useState(false)
|
||||
const router = useRouter()
|
||||
const course = useCourse() as any
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
|
||||
const openNewActivityModal = async (chapterId: any) => {
|
||||
setNewActivityModal(true)
|
||||
|
|
@ -38,7 +41,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
props.orgslug,
|
||||
{ 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`)
|
||||
setNewActivityModal(false)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
|
|
@ -52,7 +55,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
activity: any,
|
||||
chapterId: string
|
||||
) => {
|
||||
await createFileActivity(file, type, activity, chapterId)
|
||||
await createFileActivity(file, type, activity, chapterId, access_token)
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
setNewActivityModal(false)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
|
|
@ -68,7 +71,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
await createExternalVideoActivity(
|
||||
external_video_data,
|
||||
activity,
|
||||
props.chapterId
|
||||
props.chapterId, access_token
|
||||
)
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
setNewActivityModal(false)
|
||||
|
|
@ -76,7 +79,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
router.refresh()
|
||||
}
|
||||
|
||||
useEffect(() => {}, [course])
|
||||
useEffect(() => { }, [course])
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
Video,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React from 'react'
|
||||
|
|
@ -32,6 +33,8 @@ interface ModifiedActivityInterface {
|
|||
|
||||
function ActivityElement(props: ActivitiyElementProps) {
|
||||
const router = useRouter()
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const [modifiedActivity, setModifiedActivity] = React.useState<
|
||||
ModifiedActivityInterface | undefined
|
||||
>(undefined)
|
||||
|
|
@ -41,7 +44,7 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
const activityUUID = props.activity.activity_uuid
|
||||
|
||||
async function deleteActivityUI() {
|
||||
await deleteActivity(props.activity.activity_uuid)
|
||||
await deleteActivity(props.activity.activity_uuid,access_token)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
@ -60,7 +63,7 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
content: props.activity.content,
|
||||
}
|
||||
|
||||
await updateActivity(modifiedActivityCopy, activityUUID)
|
||||
await updateActivity(modifiedActivityCopy, activityUUID,access_token)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { revalidateTags } from '@services/utils/ts/requests'
|
|||
import { useRouter } from 'next/navigation'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { mutate } from 'swr'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
type ChapterElementProps = {
|
||||
chapter: any
|
||||
|
|
@ -31,6 +32,8 @@ interface ModifiedChapterInterface {
|
|||
|
||||
function ChapterElement(props: ChapterElementProps) {
|
||||
const activities = props.chapter.activities || []
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const [modifiedChapter, setModifiedChapter] = React.useState<
|
||||
ModifiedChapterInterface | undefined
|
||||
>(undefined)
|
||||
|
|
@ -41,7 +44,7 @@ function ChapterElement(props: ChapterElementProps) {
|
|||
const router = useRouter()
|
||||
|
||||
const deleteChapterUI = async () => {
|
||||
await deleteChapter(props.chapter.id)
|
||||
await deleteChapter(props.chapter.id, access_token)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
@ -53,7 +56,7 @@ function ChapterElement(props: ChapterElementProps) {
|
|||
let modifiedChapterCopy = {
|
||||
name: modifiedChapter.chapterName,
|
||||
}
|
||||
await updateChapter(chapterId, modifiedChapterCopy)
|
||||
await updateChapter(chapterId, modifiedChapterCopy, access_token)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { Hexagon } from 'lucide-react'
|
||||
import Modal from '@components/StyledElements/Modal/Modal'
|
||||
import NewChapterModal from '@components/Objects/Modals/Chapters/NewChapter'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
type EditCourseStructureProps = {
|
||||
orgslug: string
|
||||
|
|
@ -38,6 +39,8 @@ export type OrderPayload =
|
|||
|
||||
const EditCourseStructure = (props: EditCourseStructureProps) => {
|
||||
const router = useRouter()
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
// Check window availability
|
||||
const [winReady, setwinReady] = useState(false)
|
||||
|
||||
|
|
@ -57,7 +60,7 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
|
|||
|
||||
// Submit new chapter
|
||||
const submitChapter = async (chapter: any) => {
|
||||
await createChapter(chapter)
|
||||
await createChapter(chapter,access_token)
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { UploadCloud } from 'lucide-react'
|
|||
import { revalidateTags } from '@services/utils/ts/requests'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
interface OrganizationValues {
|
||||
name: string
|
||||
|
|
@ -20,7 +21,9 @@ interface OrganizationValues {
|
|||
|
||||
function OrgEditGeneral(props: any) {
|
||||
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
|
||||
// ...
|
||||
|
||||
|
|
@ -34,7 +37,7 @@ function OrgEditGeneral(props: any) {
|
|||
const uploadLogo = async () => {
|
||||
if (selectedFile) {
|
||||
let org_id = org.id
|
||||
await uploadOrganizationLogo(org_id, selectedFile)
|
||||
await uploadOrganizationLogo(org_id, selectedFile, access_token)
|
||||
setSelectedFile(null) // Reset the selected file
|
||||
await revalidateTags(['organizations'], org.slug)
|
||||
router.refresh()
|
||||
|
|
@ -51,14 +54,14 @@ function OrgEditGeneral(props: any) {
|
|||
|
||||
const updateOrg = async (values: OrganizationValues) => {
|
||||
let org_id = org.id
|
||||
await updateOrganization(org_id, values)
|
||||
await updateOrganization(org_id, values, access_token)
|
||||
|
||||
// Mutate the org
|
||||
await revalidateTags(['organizations'], org.slug)
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
useEffect(() => {}, [org])
|
||||
useEffect(() => { }, [org])
|
||||
|
||||
return (
|
||||
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
'use client'
|
||||
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 LearnHouseDashboardLogo from '@public/dashLogo.png'
|
||||
import { logout } from '@services/auth/auth'
|
||||
import { BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useEffect } from 'react'
|
||||
import UserAvatar from '../../Objects/UserAvatar'
|
||||
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
|
||||
|
||||
function LeftMenu() {
|
||||
const org = useOrg() as any
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
const route = useRouter()
|
||||
|
||||
function waitForEverythingToLoad() {
|
||||
if (org && session) {
|
||||
|
|
@ -26,9 +25,9 @@ function LeftMenu() {
|
|||
}
|
||||
|
||||
async function logOutUI() {
|
||||
const res = await logout()
|
||||
const res = await signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/login?orgslug=' + org.slug) })
|
||||
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 items-center flex-col space-y-2">
|
||||
<ToolTip
|
||||
content={'@' + session.user.username}
|
||||
content={'@' + session.data.user.username}
|
||||
slateBlack
|
||||
sideOffset={8}
|
||||
side="right"
|
||||
|
|
@ -134,7 +133,7 @@ function LeftMenu() {
|
|||
</ToolTip>
|
||||
<div className="flex items-center flex-col space-y-1">
|
||||
<ToolTip
|
||||
content={session.user.username + "'s Settings"}
|
||||
content={session.data.user.username + "'s Settings"}
|
||||
slateBlack
|
||||
sideOffset={8}
|
||||
side="right"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import { useRouter } from 'next/navigation'
|
|||
import React, { useEffect } from 'react'
|
||||
import { mutate } from 'swr'
|
||||
import { updateCourse } from '@services/courses/courses'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function SaveState(props: { orgslug: string }) {
|
||||
const course = useCourse() as any
|
||||
const session = useLHSession() as any;
|
||||
const router = useRouter()
|
||||
const saved = course ? course.isSaved : true
|
||||
const dispatchCourse = useCourseDispatch() as any
|
||||
|
|
@ -37,7 +39,8 @@ function SaveState(props: { orgslug: string }) {
|
|||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
await updateCourseOrderStructure(
|
||||
course.courseStructure.course_uuid,
|
||||
course.courseOrder
|
||||
course.courseOrder,
|
||||
session.data?.tokens?.access_token
|
||||
)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
@ -49,7 +52,8 @@ function SaveState(props: { orgslug: string }) {
|
|||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`)
|
||||
await updateCourse(
|
||||
course.courseStructure.course_uuid,
|
||||
course.courseStructure
|
||||
course.courseStructure,
|
||||
session.data?.tokens?.access_token
|
||||
)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
'use client';
|
||||
import { updateProfile } from '@services/settings/profile'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Formik, Form, Field } from 'formik'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import {
|
||||
ArrowBigUpDash,
|
||||
Check,
|
||||
|
|
@ -13,7 +14,8 @@ import UserAvatar from '@components/Objects/UserAvatar'
|
|||
import { updateUserAvatar } from '@services/users/users'
|
||||
|
||||
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 [isLoading, setIsLoading] = React.useState(false) as any
|
||||
const [error, setError] = React.useState() as any
|
||||
|
|
@ -23,7 +25,7 @@ function UserEditGeneral() {
|
|||
const file = event.target.files[0]
|
||||
setLocalAvatar(file)
|
||||
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
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
if (res.success === false) {
|
||||
|
|
@ -35,24 +37,24 @@ function UserEditGeneral() {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {}, [session, session.user])
|
||||
useEffect(() => { }, [session, session.data])
|
||||
|
||||
return (
|
||||
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
|
||||
{session.user && (
|
||||
{session.data.user && (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={{
|
||||
username: session.user.username,
|
||||
first_name: session.user.first_name,
|
||||
last_name: session.user.last_name,
|
||||
email: session.user.email,
|
||||
bio: session.user.bio,
|
||||
username: session.data.user.username,
|
||||
first_name: session.data.user.first_name,
|
||||
last_name: session.data.user.last_name,
|
||||
email: session.data.user.email,
|
||||
bio: session.data.user.bio,
|
||||
}}
|
||||
onSubmit={(values, { setSubmitting }) => {
|
||||
setTimeout(() => {
|
||||
setSubmitting(false)
|
||||
updateProfile(values, session.user.id)
|
||||
updateProfile(values, session.data.user.id, access_token)
|
||||
}, 400)
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { updatePassword } from '@services/settings/password'
|
||||
import { Formik, Form, Field } from 'formik'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
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) => {
|
||||
let user_id = session.user.id
|
||||
await updatePassword(user_id, values)
|
||||
let user_id = session.data.user.id
|
||||
await updatePassword(user_id, values, access_token)
|
||||
}
|
||||
|
||||
useEffect(() => {}, [session])
|
||||
useEffect(() => { }, [session])
|
||||
|
||||
return (
|
||||
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
|
||||
|
|
|
|||
|
|
@ -3,27 +3,28 @@ import PageLoading from '@components/Objects/Loaders/PageLoading'
|
|||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
|
||||
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
||||
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 React, { useEffect } from 'react'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
changeSignupMechanism,
|
||||
createInviteCode,
|
||||
deleteInviteCode,
|
||||
} from '@services/organizations/invites'
|
||||
import Toast from '@components/StyledElements/Toast/Toast'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Modal from '@components/StyledElements/Modal/Modal'
|
||||
import OrgInviteCodeGenerate from '@components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function OrgAccess() {
|
||||
const org = useOrg() as any
|
||||
const session = useLHSession() as any
|
||||
const access_token = session.data.tokens.access_token;
|
||||
const { data: invites } = useSWR(
|
||||
org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
const [joinMethod, setJoinMethod] = React.useState('closed')
|
||||
|
|
@ -40,10 +41,8 @@ function OrgAccess() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
|
||||
} else {
|
||||
|
|
@ -52,7 +51,7 @@ function OrgAccess() {
|
|||
}
|
||||
|
||||
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) {
|
||||
router.refresh()
|
||||
mutate(`${getAPIUrl()}orgs/slug/${org?.slug}`)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
'use client'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import AddUserGroup from '@components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup'
|
||||
import ManageUsers from '@components/Objects/Modals/Dash/OrgUserGroups/ManageUsers'
|
||||
|
|
@ -14,17 +15,19 @@ import useSWR, { mutate } from 'swr'
|
|||
|
||||
function OrgUserGroups() {
|
||||
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 [createUserGroupModal, setCreateUserGroupModal] = React.useState(false)
|
||||
const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any
|
||||
|
||||
const { data: usergroups } = useSWR(
|
||||
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
|
||||
const deleteUserGroupUI = async (usergroup_id: any) => {
|
||||
const res = await deleteUserGroup(usergroup_id)
|
||||
const res = await deleteUserGroup(usergroup_id, access_token)
|
||||
if (res.status == 200) {
|
||||
mutate(`${getAPIUrl()}usergroups/org/${org.id}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
import RolesUpdate from '@components/Objects/Modals/Dash/OrgUsers/RolesUpdate'
|
||||
|
|
@ -14,9 +15,11 @@ import useSWR, { mutate } from 'swr'
|
|||
|
||||
function OrgUsers() {
|
||||
const org = useOrg() as any
|
||||
const session = useLHSession() as any
|
||||
const access_token = session.data.tokens.access_token;
|
||||
const { data: orgUsers } = useSWR(
|
||||
org ? `${getAPIUrl()}orgs/${org?.id}/users` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const [rolesModal, setRolesModal] = React.useState(false)
|
||||
const [selectedUser, setSelectedUser] = React.useState(null) as any
|
||||
|
|
@ -28,7 +31,7 @@ function OrgUsers() {
|
|||
}
|
||||
|
||||
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) {
|
||||
await mutate(`${getAPIUrl()}orgs/${org.id}/users`)
|
||||
} else {
|
||||
|
|
@ -39,7 +42,6 @@ function OrgUsers() {
|
|||
useEffect(() => {
|
||||
if (orgUsers) {
|
||||
setIsLoading(false)
|
||||
console.log(orgUsers)
|
||||
}
|
||||
}, [org, orgUsers])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
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 { inviteBatchUsers } from '@services/organizations/invites'
|
||||
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 toast from 'react-hot-toast'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
|
||||
function OrgUsersAdd() {
|
||||
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 [invitedUsers, setInvitedUsers] = React.useState('');
|
||||
const [selectedInviteCode, setSelectedInviteCode] = React.useState('');
|
||||
|
||||
async function sendInvites() {
|
||||
setIsLoading(true)
|
||||
let res = await inviteBatchUsers(org.id, invitedUsers, selectedInviteCode)
|
||||
let res = await inviteBatchUsers(org.id, invitedUsers, selectedInviteCode,access_token)
|
||||
if (res.status == 200) {
|
||||
mutate(`${getAPIUrl()}orgs/${org?.id}/invites/users`)
|
||||
setIsLoading(false)
|
||||
|
|
@ -31,18 +34,17 @@ function OrgUsersAdd() {
|
|||
|
||||
const { data: invites } = useSWR(
|
||||
org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
const { data: invited_users } = useSWR(
|
||||
org ? `${getAPIUrl()}orgs/${org?.id}/invites/users` : null,
|
||||
swrFetcher
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (invites) {
|
||||
setSelectedInviteCode(invites?.[0]?.invite_code_uuid)
|
||||
}
|
||||
console.log('dev,',selectedInviteCode)
|
||||
}
|
||||
, [invites, invited_users])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +1,43 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useEffect } 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()
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import {
|
||||
sendActivityAIChatMessage,
|
||||
startActivityAIChatSession,
|
||||
|
|
@ -74,7 +74,8 @@ type 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 dispatchAIChatBot = useAIChatBotDispatch() as any
|
||||
|
||||
|
|
@ -115,7 +116,8 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
const response = await sendActivityAIChatMessage(
|
||||
message,
|
||||
aiChatBotState.aichat_uuid,
|
||||
props.activity.activity_uuid
|
||||
props.activity.activity_uuid,
|
||||
access_token
|
||||
)
|
||||
if (response.success == false) {
|
||||
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
@ -143,8 +145,9 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
})
|
||||
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
|
||||
const response = await startActivityAIChatSession(
|
||||
message,
|
||||
message,access_token,
|
||||
props.activity.activity_uuid
|
||||
|
||||
)
|
||||
if (response.success == false) {
|
||||
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
@ -219,14 +222,12 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`flex space-x-2 items-center -ml-[100px] ${
|
||||
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
className={`flex space-x-2 items-center -ml-[100px] ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
className={`outline outline-1 outline-neutral-200/20 rounded-lg ${
|
||||
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
className={`outline outline-1 outline-neutral-200/20 rounded-lg ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
width={24}
|
||||
src={learnhouseAI_icon}
|
||||
alt=""
|
||||
|
|
@ -244,12 +245,11 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`w-100 h-0.5 bg-white/5 rounded-full mx-auto mb-3 ${
|
||||
aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
className={`w-100 h-0.5 bg-white/5 rounded-full mx-auto mb-3 ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''
|
||||
}`}
|
||||
></div>
|
||||
{aiChatBotState.messages.length > 0 &&
|
||||
!aiChatBotState.error.isError ? (
|
||||
!aiChatBotState.error.isError ? (
|
||||
<div className="flex-col h-[237px] w-full space-y-4 overflow-scroll scrollbar-w-2 scrollbar scrollbar-thumb-white/20 scrollbar-thumb-rounded-full scrollbar-track-rounded-full">
|
||||
{aiChatBotState.messages.map(
|
||||
(message: AIMessage, index: number) => {
|
||||
|
|
@ -328,7 +328,7 @@ type AIMessageProps = {
|
|||
}
|
||||
|
||||
function AIMessage(props: AIMessageProps) {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
|
||||
const words = props.message.message.split(' ')
|
||||
|
||||
|
|
@ -378,7 +378,7 @@ const AIMessagePlaceHolder = (props: {
|
|||
activity_uuid: string
|
||||
sendMessage: any
|
||||
}) => {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const [feedbackModal, setFeedbackModal] = React.useState(false)
|
||||
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ const AIMessagePlaceHolder = (props: {
|
|||
<span className="items-center">Hello</span>
|
||||
<span className="capitalize flex space-x-2 items-center">
|
||||
<UserAvatar rounded="rounded-lg" border="border-2" width={35} />
|
||||
<span>{session.user.username},</span>
|
||||
<span>{session.data.user.username},</span>
|
||||
</span>
|
||||
<span>how can we help today ?</span>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ function DocumentPdfActivity({
|
|||
const org = useOrg() as any
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log(activity)
|
||||
}, [activity, org])
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
startActivityAIChatSession,
|
||||
} from '@services/ai/ai'
|
||||
import useGetAIFeatures from '../../../../AI/Hooks/useGetAIFeatures'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
type AICanvaToolkitProps = {
|
||||
editor: Editor
|
||||
|
|
@ -92,6 +93,8 @@ function AIActionButton(props: {
|
|||
label: string
|
||||
activity: any
|
||||
}) {
|
||||
const session = useLHSession() as any
|
||||
const access_token = session.data.tokens.access_token;
|
||||
const dispatchAIChatBot = useAIChatBotDispatch() as any
|
||||
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
|
||||
|
||||
|
|
@ -132,7 +135,7 @@ function AIActionButton(props: {
|
|||
const response = await sendActivityAIChatMessage(
|
||||
message,
|
||||
aiChatBotState.aichat_uuid,
|
||||
props.activity.activity_uuid
|
||||
props.activity.activity_uuid, access_token
|
||||
)
|
||||
if (response.success == false) {
|
||||
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
@ -160,8 +163,7 @@ function AIActionButton(props: {
|
|||
})
|
||||
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
|
||||
const response = await startActivityAIChatSession(
|
||||
message,
|
||||
props.activity.activity_uuid
|
||||
message, access_token
|
||||
)
|
||||
if (response.success == false) {
|
||||
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
@ -193,10 +195,10 @@ function AIActionButton(props: {
|
|||
props.label === 'Explain'
|
||||
? 'Explain a word or a sentence with AI'
|
||||
: props.label === 'Summarize'
|
||||
? 'Summarize a long paragraph or text with AI'
|
||||
: props.label === 'Translate'
|
||||
? 'Translate to different languages with AI'
|
||||
: 'Give examples to understand better with AI'
|
||||
? 'Summarize a long paragraph or text with AI'
|
||||
: props.label === 'Translate'
|
||||
? 'Translate to different languages with AI'
|
||||
: 'Give examples to understand better with AI'
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
<ToolTip sideOffset={10} slateBlack content={tooltipLabel}>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,6 @@ function VideoActivity({ activity, course }: { activity: any; course: any }) {
|
|||
const org = useOrg() as any
|
||||
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(() => {
|
||||
if (activity && activity.content && activity.content.uri) {
|
||||
var getYouTubeID = require('get-youtube-id');
|
||||
|
|
|
|||
|
|
@ -20,12 +20,15 @@ import toast from 'react-hot-toast'
|
|||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
function CourseUpdates() {
|
||||
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)
|
||||
|
||||
function handleModelOpen() {
|
||||
|
|
@ -35,7 +38,6 @@ function CourseUpdates() {
|
|||
// if user clicks outside the model, close the model
|
||||
React.useLayoutEffect(() => {
|
||||
function handleClickOutside(event: any) {
|
||||
console.log(event.target.id)
|
||||
if (event.target.closest('.bg-white') || event.target.id === 'delete-update-button') return;
|
||||
setIsModelOpen(false);
|
||||
}
|
||||
|
|
@ -71,7 +73,7 @@ function CourseUpdates() {
|
|||
|
||||
const UpdatesSection = () => {
|
||||
const [selectedView, setSelectedView] = React.useState('list')
|
||||
const isAdmin = useAdminStatus() as boolean;
|
||||
const adminStatus = useAdminStatus() ;
|
||||
return (
|
||||
<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'>
|
||||
|
|
@ -80,7 +82,7 @@ const UpdatesSection = () => {
|
|||
<span>Updates</span>
|
||||
|
||||
</div>
|
||||
{isAdmin && <div
|
||||
{adminStatus.isAdmin && <div
|
||||
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'>
|
||||
<PencilLine size={14} />
|
||||
|
|
@ -98,6 +100,7 @@ const UpdatesSection = () => {
|
|||
const NewUpdateForm = ({ setSelectedView }: any) => {
|
||||
const org = useOrg() as any;
|
||||
const course = useCourse() as any;
|
||||
const session = useLHSession() as any;
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -124,7 +127,7 @@ const NewUpdateForm = ({ setSelectedView }: any) => {
|
|||
course_uuid: course.courseStructure.course_uuid,
|
||||
org_id: org.id
|
||||
}
|
||||
const res = await createCourseUpdate(body)
|
||||
const res = await createCourseUpdate(body, session.data?.tokens?.access_token)
|
||||
if (res.status === 200) {
|
||||
toast.success('Update added successfully')
|
||||
setSelectedView('list')
|
||||
|
|
@ -192,23 +195,25 @@ const NewUpdateForm = ({ setSelectedView }: any) => {
|
|||
|
||||
const UpdatesListView = () => {
|
||||
const course = useCourse() as any;
|
||||
const isAdmin = useAdminStatus() as boolean;
|
||||
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, swrFetcher)
|
||||
const adminStatus = useAdminStatus() ;
|
||||
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 (
|
||||
<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 className='font-bold text-gray-500 flex space-x-2 items-center justify-between '>
|
||||
<div className='flex space-x-2 items-center'>
|
||||
<span> {update.title}</span>
|
||||
<span
|
||||
title={"Created at " + dayjs(update.creation_date).format('MMMM D, YYYY')}
|
||||
className='text-xs font-semibold text-gray-300'>
|
||||
{dayjs(update.creation_date).fromNow()}
|
||||
title={"Created at " + dayjs(update.creation_date).format('MMMM D, YYYY')}
|
||||
className='text-xs font-semibold text-gray-300'>
|
||||
{dayjs(update.creation_date).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
{isAdmin && <DeleteUpdateButton update={update} />}</div>
|
||||
{adminStatus.isAdmin && !adminStatus.loading && <DeleteUpdateButton update={update} />}</div>
|
||||
<div className='text-gray-600'>{update.content}</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -223,11 +228,12 @@ const UpdatesListView = () => {
|
|||
}
|
||||
|
||||
const DeleteUpdateButton = ({ update }: any) => {
|
||||
const session = useLHSession() as any;
|
||||
const course = useCourse() as any;
|
||||
const org = useOrg() as any;
|
||||
|
||||
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) {
|
||||
toast.success('Update deleted successfully')
|
||||
mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
startActivityAIChatSession,
|
||||
} from '@services/ai/ai'
|
||||
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
type AIEditorToolkitProps = {
|
||||
editor: Editor
|
||||
|
|
@ -32,11 +33,11 @@ type AIEditorToolkitProps = {
|
|||
|
||||
type AIPromptsLabels = {
|
||||
label:
|
||||
| 'Writer'
|
||||
| 'ContinueWriting'
|
||||
| 'MakeLonger'
|
||||
| 'GenerateQuiz'
|
||||
| 'Translate'
|
||||
| 'Writer'
|
||||
| 'ContinueWriting'
|
||||
| 'MakeLonger'
|
||||
| 'GenerateQuiz'
|
||||
| 'Translate'
|
||||
selection: string
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +142,8 @@ function AIEditorToolkit(props: AIEditorToolkitProps) {
|
|||
const UserFeedbackModal = (props: AIEditorToolkitProps) => {
|
||||
const dispatchAIEditor = useAIEditorDispatch() as any
|
||||
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>) => {
|
||||
await dispatchAIEditor({
|
||||
|
|
@ -159,7 +162,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
|
|||
const response = await sendActivityAIChatMessage(
|
||||
message,
|
||||
aiEditorState.aichat_uuid,
|
||||
props.activity.activity_uuid
|
||||
props.activity.activity_uuid, access_token
|
||||
)
|
||||
if (response.success === false) {
|
||||
await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
@ -191,7 +194,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
|
|||
})
|
||||
await dispatchAIEditor({ type: 'setIsWaitingForResponse' })
|
||||
const response = await startActivityAIChatSession(
|
||||
message,
|
||||
message, access_token,
|
||||
props.activity.activity_uuid
|
||||
)
|
||||
if (response.success === false) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { getUserAvatarMediaDirectory } from '@services/media/media';
|
||||
import { getCollaborationServerUrl } from '@services/config/config';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
|
|
@ -11,7 +11,7 @@ type ActiveAvatarsProps = {
|
|||
}
|
||||
|
||||
function ActiveAvatars(props: ActiveAvatarsProps) {
|
||||
const session = useSession() as any;
|
||||
const session = useLHSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const [activeUsers, setActiveUsers] = useState({} as any);
|
||||
|
||||
|
|
@ -27,11 +27,11 @@ function ActiveAvatars(props: ActiveAvatarsProps) {
|
|||
});
|
||||
|
||||
// Remove the current user from the list
|
||||
delete users[session.user.user_uuid];
|
||||
delete users[session.data.user.user_uuid];
|
||||
|
||||
setActiveUsers(users);
|
||||
}
|
||||
, [props.mouseMovements, session.user, org]);
|
||||
, [props.mouseMovements, session.data.user, org]);
|
||||
|
||||
|
||||
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>
|
||||
))}
|
||||
{session.isAuthenticated && (
|
||||
{session.status && (
|
||||
<div className='z-50'>
|
||||
<UserAvatar
|
||||
width={40}
|
||||
|
|
|
|||
|
|
@ -41,11 +41,9 @@ import html from 'highlight.js/lib/languages/xml'
|
|||
import python from 'highlight.js/lib/languages/python'
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import { CourseProvider } from '@components/Contexts/CourseContext'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import AIEditorToolkit from './AI/AIEditorToolkit'
|
||||
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import randomColor from 'randomcolor'
|
||||
import Collaboration from '@tiptap/extension-collaboration'
|
||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||
import ActiveAvatars from './ActiveAvatars'
|
||||
|
|
@ -65,7 +63,7 @@ interface Editor {
|
|||
}
|
||||
|
||||
function Editor(props: Editor) {
|
||||
const session = useSession() as any
|
||||
const session = useLHSession() as any
|
||||
const dispatchAIEditor = useAIEditorDispatch() as any
|
||||
const aiEditorState = useAIEditor() as AIEditorStateTypes
|
||||
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'editor' })
|
||||
|
|
@ -145,7 +143,7 @@ function Editor(props: Editor) {
|
|||
CollaborationCursor.configure({
|
||||
provider: props.hocuspocusProvider,
|
||||
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,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { updateActivity } from '@services/courses/activities'
|
|||
import { toast } from 'react-hot-toast'
|
||||
import Toast from '@components/StyledElements/Toast/Toast'
|
||||
import { OrgProvider } from '@components/Contexts/OrgContext'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
// Collaboration
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||
|
|
@ -24,7 +24,8 @@ interface EditorWrapperProps {
|
|||
}
|
||||
|
||||
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
|
||||
const [provider, setProvider] = React.useState<HocuspocusProvider | null>(null);
|
||||
const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string)
|
||||
|
|
@ -57,7 +58,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
|||
document.addEventListener("mousemove", (event) => {
|
||||
// Share any information you like
|
||||
provider?.setAwarenessField("userMouseMovement", {
|
||||
user: session.user,
|
||||
user: session.data.user,
|
||||
mouseX: event.clientX,
|
||||
mouseY: event.clientY,
|
||||
color: thisPageColor,
|
||||
|
|
@ -72,14 +73,14 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
|||
|
||||
|
||||
provider?.setAwarenessField("savings_states", {
|
||||
[session.user.user_uuid]: {
|
||||
[session.data.user.user_uuid]: {
|
||||
status: 'action_save',
|
||||
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...',
|
||||
success: <b>Activity saved!</b>,
|
||||
error: <b>Could not save.</b>,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ import { getActivityBlockMediaDirectory } from '@services/media/media'
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useCourse } from '@components/Contexts/CourseContext'
|
||||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function ImageBlockComponent(props: any) {
|
||||
const org = useOrg() as any
|
||||
const course = useCourse() 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 [image, setImage] = React.useState(null)
|
||||
|
|
@ -36,7 +39,7 @@ function ImageBlockComponent(props: any) {
|
|||
setIsLoading(true)
|
||||
let object = await uploadNewImageFile(
|
||||
image,
|
||||
props.extension.options.activity.activity_uuid
|
||||
props.extension.options.activity.activity_uuid,access_token
|
||||
)
|
||||
setIsLoading(false)
|
||||
setblockObject(object)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import { getActivityBlockMediaDirectory } from '@services/media/media'
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useCourse } from '@components/Contexts/CourseContext'
|
||||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function PDFBlockComponent(props: any) {
|
||||
const org = useOrg() 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 [isLoading, setIsLoading] = React.useState(false)
|
||||
const [blockObject, setblockObject] = React.useState(
|
||||
|
|
@ -32,7 +35,7 @@ function PDFBlockComponent(props: any) {
|
|||
setIsLoading(true)
|
||||
let object = await uploadNewPDFFile(
|
||||
pdf,
|
||||
props.extension.options.activity.activity_uuid
|
||||
props.extension.options.activity.activity_uuid, access_token
|
||||
)
|
||||
setIsLoading(false)
|
||||
setblockObject(object)
|
||||
|
|
@ -41,7 +44,7 @@ function PDFBlockComponent(props: any) {
|
|||
})
|
||||
}
|
||||
|
||||
useEffect(() => {}, [course, org])
|
||||
useEffect(() => { }, [course, org])
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="block-pdf">
|
||||
|
|
|
|||
|
|
@ -86,10 +86,8 @@ function QuizBlockComponent(props: any) {
|
|||
|
||||
if (allCorrect) {
|
||||
setSubmissionMessage('All answers are correct!')
|
||||
console.log('All answers are correct!')
|
||||
} else {
|
||||
setSubmissionMessage('Some answers are incorrect!')
|
||||
console.log('Some answers are incorrect!')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { UploadIcon } from '@radix-ui/react-icons'
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { useCourse } from '@components/Contexts/CourseContext'
|
||||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function VideoBlockComponents(props: any) {
|
||||
const org = useOrg() as any
|
||||
|
|
@ -15,6 +16,8 @@ function VideoBlockComponents(props: any) {
|
|||
const editorState = useEditorProvider() as any
|
||||
const isEditable = editorState.isEditable
|
||||
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 [blockObject, setblockObject] = React.useState(
|
||||
props.node.attrs.blockObject
|
||||
|
|
@ -32,7 +35,7 @@ function VideoBlockComponents(props: any) {
|
|||
setIsLoading(true)
|
||||
let object = await uploadNewVideoFile(
|
||||
video,
|
||||
props.extension.options.activity.activity_uuid
|
||||
props.extension.options.activity.activity_uuid, access_token
|
||||
)
|
||||
setIsLoading(false)
|
||||
setblockObject(object)
|
||||
|
|
@ -41,7 +44,7 @@ function VideoBlockComponents(props: any) {
|
|||
})
|
||||
}
|
||||
|
||||
useEffect(() => {}, [course, org])
|
||||
useEffect(() => { }, [course, org])
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="block-video">
|
||||
|
|
@ -98,7 +101,7 @@ function VideoBlockComponents(props: any) {
|
|||
)
|
||||
}
|
||||
const BlockVideoWrapper = styled.div`
|
||||
//border: ${(props) =>
|
||||
border: ${(props) =>
|
||||
props.contentEditable ? '2px dashed #713f1117' : 'none'};
|
||||
|
||||
// center
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
'use client'
|
||||
import React from 'react'
|
||||
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 MenuLinks from './MenuLinks'
|
||||
import { getOrgLogoMediaDirectory } from '@services/media/media'
|
||||
import useSWR from 'swr'
|
||||
import { swrFetcher } from '@services/utils/ts/requests'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
|
||||
export const Menu = (props: any) => {
|
||||
const orgslug = props.orgslug
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const [feedbackModal, setFeedbackModal] = React.useState(false)
|
||||
const {
|
||||
data: org,
|
||||
error: error,
|
||||
isLoading,
|
||||
} = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher)
|
||||
const org = useOrg() as any;
|
||||
|
||||
function closeFeedbackModal() {
|
||||
setFeedbackModal(false)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ import React, { useState } from 'react'
|
|||
import { BarLoader } from 'react-spinners'
|
||||
import { revalidateTags } from '@services/utils/ts/requests'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
function CreateCourseModal({ closeModal, orgslug }: any) {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const session = useLHSession() as any;
|
||||
const [name, setName] = React.useState('')
|
||||
const [description, setDescription] = React.useState('')
|
||||
const [learnings, setLearnings] = React.useState('')
|
||||
|
|
@ -53,7 +55,6 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
|
||||
const handleVisibilityChange = (event: React.ChangeEvent<any>) => {
|
||||
setVisibility(event.target.value)
|
||||
console.log(visibility)
|
||||
}
|
||||
|
||||
const handleTagsChange = (event: React.ChangeEvent<any>) => {
|
||||
|
|
@ -71,7 +72,8 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
let status = await createNewCourse(
|
||||
orgId,
|
||||
{ name, description, tags, visibility },
|
||||
thumbnail
|
||||
thumbnail,
|
||||
session.data?.tokens?.access_token
|
||||
)
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
setIsSubmitting(false)
|
||||
|
|
@ -80,9 +82,6 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
closeModal()
|
||||
router.refresh()
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
|
||||
// refresh page (FIX for Next.js BUG)
|
||||
// window.location.reload();
|
||||
} else {
|
||||
alert('Error creating course, please see console logs')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
'use client';
|
||||
import { useCourse } from '@components/Contexts/CourseContext';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { linkResourcesToUserGroup } from '@services/usergroups/usergroups';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import { AlertTriangle, Info } from 'lucide-react';
|
||||
import { Info } from 'lucide-react';
|
||||
import React, { useEffect } from 'react'
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR, { mutate } from 'swr'
|
||||
|
|
@ -17,6 +18,8 @@ type LinkToUserGroupProps = {
|
|||
function LinkToUserGroup(props: LinkToUserGroupProps) {
|
||||
const course = useCourse() 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 { data: usergroups } = useSWR(
|
||||
|
|
@ -27,8 +30,7 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
|
|||
|
||||
|
||||
const handleLink = async () => {
|
||||
console.log('selectedUserGroup', selectedUserGroup)
|
||||
const res = await linkResourcesToUserGroup(selectedUserGroup, courseStructure.course_uuid)
|
||||
const res = await linkResourcesToUserGroup(selectedUserGroup, courseStructure.course_uuid, access_token)
|
||||
if (res.status === 200) {
|
||||
props.setUserGroupModal(false)
|
||||
toast.success('Successfully linked to usergroup')
|
||||
|
|
@ -37,7 +39,6 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
|
|||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -55,22 +56,22 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
|
|||
</div>
|
||||
<div className='p-4 flex-row flex justify-between items-center'>
|
||||
|
||||
<div className='py-1'>
|
||||
<span className='px-3 text-gray-400 font-bold rounded-full py-1 bg-gray-100 mx-3'>UserGroup Name </span>
|
||||
<select
|
||||
onChange={(e) => setSelectedUserGroup(e.target.value)}
|
||||
defaultValue={selectedUserGroup}
|
||||
>
|
||||
{usergroups && usergroups.map((group: any) => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
))}
|
||||
<div className='py-1'>
|
||||
<span className='px-3 text-gray-400 font-bold rounded-full py-1 bg-gray-100 mx-3'>UserGroup Name </span>
|
||||
<select
|
||||
onChange={(e) => setSelectedUserGroup(e.target.value)}
|
||||
defaultValue={selectedUserGroup}
|
||||
>
|
||||
{usergroups && usergroups.map((group: any) => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
))}
|
||||
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
<div className='py-3'>
|
||||
<button onClick={() => { handleLink() }} className='bg-green-700 text-white font-bold px-4 py-2 rounded-md shadow'>Link</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-3'>
|
||||
<button onClick={() => { handleLink() }} className='bg-green-700 text-white font-bold px-4 py-2 rounded-md shadow'>Link</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { useOrg } from '@components/Contexts/OrgContext'
|
|||
import { getAPIUrl } from '@services/config/config'
|
||||
import { createInviteCode, createInviteCodeWithUserGroup } from '@services/organizations/invites'
|
||||
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 toast from 'react-hot-toast'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
|
|
@ -13,6 +14,8 @@ type OrgInviteCodeGenerateProps = {
|
|||
|
||||
function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
|
||||
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 { data: usergroups } = useSWR(
|
||||
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
||||
|
|
@ -20,7 +23,7 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
|
|||
)
|
||||
|
||||
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) {
|
||||
mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
|
||||
props.setInvitesModal(false)
|
||||
|
|
@ -30,7 +33,7 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
|
|||
}
|
||||
|
||||
async function createInvite() {
|
||||
let res = await createInviteCode(org.id)
|
||||
let res = await createInviteCode(org.id, session.data?.tokens?.access_token)
|
||||
if (res.status == 200) {
|
||||
mutate(`${getAPIUrl()}orgs/${org.id}/invites`)
|
||||
props.setInvitesModal(false)
|
||||
|
|
|
|||
|
|
@ -14,13 +14,16 @@ import { BarLoader } from 'react-spinners'
|
|||
import { createUserGroup } from '@services/usergroups/usergroups'
|
||||
import { mutate } from 'swr'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
|
||||
type AddUserGroupProps = {
|
||||
setCreateUserGroupModal: any
|
||||
}
|
||||
|
||||
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 [userGroupDescription, setUserGroupDescription] = React.useState('')
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
|
|
@ -42,7 +45,7 @@ function AddUserGroup(props: AddUserGroupProps) {
|
|||
description: userGroupDescription,
|
||||
org_id: org.id
|
||||
}
|
||||
const res = await createUserGroup(obj)
|
||||
const res = await createUserGroup(obj, access_token)
|
||||
if (res.status == 200) {
|
||||
setIsSubmitting(false)
|
||||
mutate(`${getAPIUrl()}usergroups/org/${org.id}`)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { linkUserToUserGroup, unLinkUserToUserGroup } from '@services/usergroups/usergroups'
|
||||
|
|
@ -14,6 +15,8 @@ type ManageUsersProps = {
|
|||
|
||||
function ManageUsers(props: ManageUsersProps) {
|
||||
const org = useOrg() as any
|
||||
const session = useLHSession() as any
|
||||
const access_token = session.data.tokens.access_token;
|
||||
const { data: OrgUsers } = useSWR(
|
||||
org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
|
||||
swrFetcher
|
||||
|
|
@ -31,7 +34,7 @@ function ManageUsers(props: ManageUsersProps) {
|
|||
}
|
||||
|
||||
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) {
|
||||
toast.success('User linked successfully')
|
||||
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||
|
|
@ -41,7 +44,7 @@ function ManageUsers(props: ManageUsersProps) {
|
|||
}
|
||||
|
||||
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) {
|
||||
toast.success('User unlinked successfully')
|
||||
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue