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