Merge pull request #400 from learnhouse/fix/misc-bugs

Fix Misc Bugs & Issues
This commit is contained in:
Badr B. 2024-11-29 00:18:58 +01:00 committed by GitHub
commit 8c7b4a3f7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 214 additions and 92 deletions

View file

@ -67,3 +67,33 @@ def migrate_v0_to_v1(v0_config):
} }
return v1_config return v1_config
def migrate_to_v1_1(v1_config):
# Start by copying the existing configuration
v1_1_config = v1_config.copy()
# Update the config version
v1_1_config["config_version"] = "1.1"
# Add the new 'cloud' object at the end
v1_1_config['cloud'] = {
"plan": "free",
"custom_domain": False
}
return v1_1_config
def migrate_to_v1_2(v1_1_config):
v1_2_config = v1_1_config.copy()
v1_2_config['config_version'] = '1.2'
# Enable payments for everyone
v1_2_config['features']['payments']['enabled'] = True
# Only delete stripe_key if it exists
if 'stripe_key' in v1_2_config['features']['payments']:
del v1_2_config['features']['payments']['stripe_key']
return v1_2_config

View file

@ -1,14 +0,0 @@
def migrate_to_v1_1(v1_config):
# Start by copying the existing configuration
v1_1_config = v1_config.copy()
# Update the config version
v1_1_config["config_version"] = "1.1"
# Add the new 'cloud' object at the end
v1_1_config['cloud'] = {
"plan": "free",
"custom_domain": False
}
return v1_1_config

View file

@ -1,8 +1,7 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlmodel import Session, select from sqlmodel import Session, select
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
from migrations.orgconfigs.v0tov1 import migrate_v0_to_v1 from migrations.orgconfigs.orgconfigs_migrations import migrate_to_v1_1, migrate_to_v1_2, migrate_v0_to_v1
from migrations.orgconfigs.v1_1 import migrate_to_v1_1
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.db.organization_config import OrganizationConfig from src.db.organization_config import OrganizationConfig
@ -52,3 +51,21 @@ async def migratev1_1(
db_session.commit() db_session.commit()
return {"message": "Migration successful"} return {"message": "Migration successful"}
@router.post("/migrate_orgconfig_v1_to_v1.2")
async def migratev1_2(
db_session: Session = Depends(get_db_session),
):
"""
Migrate organization config from v0 to v1
"""
statement = select(OrganizationConfig)
result = db_session.exec(statement)
for orgConfig in result:
orgConfig.config = migrate_to_v1_2(orgConfig.config)
db_session.add(orgConfig)
db_session.commit()
return {"message": "Migration successful"}

View file

@ -16,7 +16,7 @@ from src.db.usergroup_resources import UserGroupResource
from src.db.usergroup_user import UserGroupUser from src.db.usergroup_user import UserGroupUser
from src.db.organizations import Organization from src.db.organizations import Organization
from src.db.usergroups import UserGroup, UserGroupCreate, UserGroupRead, UserGroupUpdate from src.db.usergroups import UserGroup, UserGroupCreate, UserGroupRead, UserGroupUpdate
from src.db.users import AnonymousUser, PublicUser, User, UserRead from src.db.users import AnonymousUser, InternalUser, PublicUser, User, UserRead
async def create_usergroup( async def create_usergroup(
@ -275,7 +275,7 @@ async def delete_usergroup_by_id(
async def add_users_to_usergroup( async def add_users_to_usergroup(
request: Request, request: Request,
db_session: Session, db_session: Session,
current_user: PublicUser | AnonymousUser, current_user: PublicUser | AnonymousUser | InternalUser,
usergroup_id: int, usergroup_id: int,
user_ids: str, user_ids: str,
) -> str: ) -> str:
@ -486,10 +486,13 @@ async def remove_resources_from_usergroup(
async def rbac_check( async def rbac_check(
request: Request, request: Request,
usergroup_uuid: str, usergroup_uuid: str,
current_user: PublicUser | AnonymousUser, current_user: PublicUser | AnonymousUser | InternalUser,
action: Literal["create", "read", "update", "delete"], action: Literal["create", "read", "update", "delete"],
db_session: Session, db_session: Session,
): ):
if isinstance(current_user, InternalUser):
return True
await authorization_verify_if_user_is_anon(current_user.id) await authorization_verify_if_user_is_anon(current_user.id)
await authorization_verify_based_on_roles_and_authorship( await authorization_verify_based_on_roles_and_authorship(

View file

@ -21,6 +21,7 @@ from src.security.rbac.rbac import (
from src.db.organizations import Organization, OrganizationRead from src.db.organizations import Organization, OrganizationRead
from src.db.users import ( from src.db.users import (
AnonymousUser, AnonymousUser,
InternalUser,
PublicUser, PublicUser,
User, User,
UserCreate, UserCreate,
@ -147,19 +148,21 @@ async def create_user_with_invite(
# Usage check # Usage check
check_limits_with_usage("members", org_id, db_session) check_limits_with_usage("members", org_id, db_session)
user = await create_user(request, db_session, current_user, user_object, org_id)
# Check if invite code contains UserGroup # Check if invite code contains UserGroup
if inviteCode.usergroup_id: if inviteCode.get("usergroup_id"):
# Add user to UserGroup # Add user to UserGroup
await add_users_to_usergroup( await add_users_to_usergroup(
request, request,
db_session, db_session,
current_user, InternalUser(id=0),
inviteCode.usergroup_id, int(inviteCode.get("usergroup_id")), # Convert to int since usergroup_id is expected to be int
user_object.username, str(user.id),
) )
user = await create_user(request, db_session, current_user, user_object, org_id)
increase_feature_usage("members", org_id, db_session) increase_feature_usage("members", org_id, db_session)
return user return user

View file

@ -3,7 +3,7 @@ import learnhouseIcon from 'public/learnhouse_bigicon_1.png'
import Image from 'next/image' 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, getUriWithoutOrg } from '@services/config/config'
import { useLHSession } from '@components/Contexts/LHSessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { MailWarning, Ticket, UserPlus } from 'lucide-react' import { MailWarning, Ticket, UserPlus } from 'lucide-react'
@ -191,7 +191,7 @@ const NoTokenScreen = (props: any) => {
"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}&orgslug=${org.slug}`) router.push(getUriWithoutOrg(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`))
}, 2000) }, 2000)
} else { } else {
toast.error('Invite code is invalid') toast.error('Invite code is invalid')

View file

@ -74,8 +74,12 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
const removeOption = (qIndex: number, oIndex: number) => { const removeOption = (qIndex: number, oIndex: number) => {
const updatedQuestions = [...questions]; const updatedQuestions = [...questions];
updatedQuestions[qIndex].options.splice(oIndex, 1); if (updatedQuestions[qIndex].options.length > 1) {
setQuestions(updatedQuestions); updatedQuestions[qIndex].options.splice(oIndex, 1);
setQuestions(updatedQuestions);
} else {
toast.error('Cannot delete the last option. At least one option is required.');
}
}; };
const addQuestion = () => { const addQuestion = () => {

View file

@ -51,7 +51,7 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
about: courseStructure?.about || '', about: courseStructure?.about || '',
learnings: courseStructure?.learnings || '', learnings: courseStructure?.learnings || '',
tags: courseStructure?.tags || '', tags: courseStructure?.tags || '',
public: courseStructure?.public || '', public: courseStructure?.public || false,
}, },
validate, validate,
onSubmit: async values => { onSubmit: async values => {

View file

@ -1,7 +1,7 @@
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 ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal' import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal'
import { getAPIUrl, getUriWithOrg } from '@services/config/config' import { getAPIUrl, getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests' import { swrFetcher } from '@services/utils/ts/requests'
import { Globe, Ticket, 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'
@ -173,14 +173,12 @@ function OrgAccess() {
<Link <Link
className="outline bg-gray-50 text-gray-600 px-2 py-1 rounded-md outline-gray-300 outline-dashed outline-1" className="outline bg-gray-50 text-gray-600 px-2 py-1 rounded-md outline-gray-300 outline-dashed outline-1"
target="_blank" target="_blank"
href={getUriWithOrg( href={getUriWithoutOrg(
org?.slug, `/signup?inviteCode=${invite.invite_code}&orgslug=${org.slug}`
`/signup?inviteCode=${invite.invite_code}`
)} )}
> >
{getUriWithOrg( {getUriWithoutOrg(
org?.slug, `/signup?inviteCode=${invite.invite_code}&orgslug=${org.slug}`
`/signup?inviteCode=${invite.invite_code}`
)} )}
</Link> </Link>
</td> </td>

View file

@ -163,7 +163,9 @@ function AIActionButton(props: {
}) })
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
const response = await startActivityAIChatSession( const response = await startActivityAIChatSession(
message, access_token message,
access_token,
props.activity.activity_uuid
) )
if (response.success == false) { if (response.success == false) {
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' }) await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })

View file

@ -6,7 +6,7 @@ import { revalidateTags } from '@services/utils/ts/requests'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useLHSession } from '@components/Contexts/LHSessionContext' import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useMediaQuery } from 'usehooks-ts' import { useMediaQuery } from 'usehooks-ts'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
import { getProductsByCourse } from '@services/payments/products' import { getProductsByCourse } from '@services/payments/products'
import { LogIn, LogOut, ShoppingCart, AlertCircle } from 'lucide-react' import { LogIn, LogOut, ShoppingCart, AlertCircle } from 'lucide-react'
import Modal from '@components/Objects/StyledElements/Modal/Modal' import Modal from '@components/Objects/StyledElements/Modal/Modal'
@ -126,7 +126,7 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
const handleCourseAction = async () => { const handleCourseAction = async () => {
if (!session.data?.user) { if (!session.data?.user) {
router.push(getUriWithOrg(orgslug, '/signup?orgslug=' + orgslug)) router.push(getUriWithoutOrg(`/signup?orgslug=${orgslug}`))
return return
} }
const action = isStarted ? removeCourse : startCourse const action = isStarted ? removeCourse : startCourse

View file

@ -135,13 +135,11 @@ const BadgesExtension: React.FC = (props: any) => {
return ( return (
<NodeViewWrapper> <NodeViewWrapper>
<div className='flex space-x-2 items-center'> <div className='flex space-x-2 items-center relative'>
<div <div className={twMerge(
className={twMerge( 'flex space-x-1 py-1.5 items-center w-fit rounded-full outline outline-2 outline-white/20 px-3.5 font-semibold nice-shadow text-sm my-2',
'flex space-x-1 py-1.5 items-center w-fit rounded-full outline outline-2 outline-white/20 px-3.5 font-semibold nice-shadow text-sm my-2', getBadgeColor(color)
getBadgeColor(color) )}>
)}
>
<div className="flex items-center justify-center space-x-1"> <div className="flex items-center justify-center space-x-1">
<span className='text'>{emoji}</span> <span className='text'>{emoji}</span>
{isEditable && ( {isEditable && (
@ -176,6 +174,7 @@ const BadgesExtension: React.FC = (props: any) => {
</div> </div>
)} )}
</div> </div>
{isEditable && ( {isEditable && (
<button <button
onClick={() => setShowPredefinedCallouts(!showPredefinedCallouts)} onClick={() => setShowPredefinedCallouts(!showPredefinedCallouts)}
@ -184,8 +183,9 @@ const BadgesExtension: React.FC = (props: any) => {
<ChevronRight size={16} /> <ChevronRight size={16} />
</button> </button>
)} )}
{isEditable && showPredefinedCallouts && ( {isEditable && showPredefinedCallouts && (
<div className='flex flex-wrap gap-2 absolute mt-8 bg-white/90 backdrop-blur-md p-2 rounded-lg nice-shadow'> <div className='flex flex-wrap gap-2 absolute top-full mt-2 left-0 bg-white/90 backdrop-blur-md p-2 rounded-lg nice-shadow z-10'>
{predefinedBadges.map((badge, index) => ( {predefinedBadges.map((badge, index) => (
<button <button
key={index} key={index}

View file

@ -1,11 +1,66 @@
import { NodeViewWrapper } from '@tiptap/react' import { NodeViewWrapper } from '@tiptap/react'
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect, useMemo } from 'react'
import { Upload, Link as LinkIcon, GripVertical, GripHorizontal, AlignCenter, Cuboid, Code } from 'lucide-react' import { Upload, Link as LinkIcon, GripVertical, GripHorizontal, AlignCenter, Cuboid, Code } from 'lucide-react'
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext' import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
import { SiGithub, SiReplit, SiSpotify, SiLoom, SiGooglemaps, SiCodepen, SiCanva, SiNotion, SiGoogledocs, SiGitlab, SiX, SiFigma, SiGiphy } from '@icons-pack/react-simple-icons' import { SiGithub, SiReplit, SiSpotify, SiLoom, SiGooglemaps, SiCodepen, SiCanva, SiNotion, SiGoogledocs, SiGitlab, SiX, SiFigma, SiGiphy } from '@icons-pack/react-simple-icons'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import DOMPurify from 'dompurify' import DOMPurify from 'dompurify'
// Add new type for script-based embeds
const SCRIPT_BASED_EMBEDS = {
twitter: { src: 'https://platform.twitter.com/widgets.js', identifier: 'twitter-tweet' },
instagram: { src: 'https://www.instagram.com/embed.js', identifier: 'instagram-media' },
tiktok: { src: 'https://www.tiktok.com/embed.js', identifier: 'tiktok-embed' },
// Add more platforms as needed
};
// Add new memoized component for the embed content
const MemoizedEmbed = React.memo(({ embedUrl, sanitizedEmbedCode, embedType }: {
embedUrl: string;
sanitizedEmbedCode: string;
embedType: 'url' | 'code';
}) => {
useEffect(() => {
if (embedType === 'code' && sanitizedEmbedCode) {
// Check for any matching script-based embeds
const matchingPlatform = Object.entries(SCRIPT_BASED_EMBEDS).find(([_, config]) =>
sanitizedEmbedCode.includes(config.identifier)
);
if (matchingPlatform) {
const [_, config] = matchingPlatform;
const script = document.createElement('script');
script.src = config.src;
script.async = true;
script.charset = 'utf-8';
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}
}
}, [embedType, sanitizedEmbedCode]);
if (embedType === 'url' && embedUrl) {
return (
<iframe
src={embedUrl}
className="w-full h-full"
frameBorder="0"
allowFullScreen
/>
);
}
if (embedType === 'code' && sanitizedEmbedCode) {
return <div dangerouslySetInnerHTML={{ __html: sanitizedEmbedCode }} className="w-full h-full" />;
}
return null;
});
MemoizedEmbed.displayName = 'MemoizedEmbed';
function EmbedObjectsComponent(props: any) { function EmbedObjectsComponent(props: any) {
const [embedType, setEmbedType] = useState<'url' | 'code'>(props.node.attrs.embedType || 'url') const [embedType, setEmbedType] = useState<'url' | 'code'>(props.node.attrs.embedType || 'url')
const [embedUrl, setEmbedUrl] = useState(props.node.attrs.embedUrl || '') const [embedUrl, setEmbedUrl] = useState(props.node.attrs.embedUrl || '')
@ -13,6 +68,7 @@ function EmbedObjectsComponent(props: any) {
const [embedHeight, setEmbedHeight] = useState(props.node.attrs.embedHeight || 300) const [embedHeight, setEmbedHeight] = useState(props.node.attrs.embedHeight || 300)
const [embedWidth, setEmbedWidth] = useState(props.node.attrs.embedWidth || '100%') const [embedWidth, setEmbedWidth] = useState(props.node.attrs.embedWidth || '100%')
const [alignment, setAlignment] = useState(props.node.attrs.alignment || 'left') const [alignment, setAlignment] = useState(props.node.attrs.alignment || 'left')
const [isResizing, setIsResizing] = useState(false)
const resizeRef = useRef<HTMLDivElement>(null) const resizeRef = useRef<HTMLDivElement>(null)
const editorState = useEditorProvider() as any const editorState = useEditorProvider() as any
@ -53,26 +109,40 @@ function EmbedObjectsComponent(props: any) {
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newUrl = event.target.value; const newUrl = event.target.value;
// Sanitize the URL const trimmedUrl = newUrl.trim();
const sanitizedUrl = DOMPurify.sanitize(newUrl); // Only update if URL is not just whitespace
setEmbedUrl(sanitizedUrl); if (newUrl === '' || trimmedUrl) {
props.updateAttributes({ const sanitizedUrl = DOMPurify.sanitize(newUrl);
embedUrl: sanitizedUrl, setEmbedUrl(sanitizedUrl);
embedType: 'url', props.updateAttributes({
}); embedUrl: sanitizedUrl,
embedType: 'url',
});
}
}; };
const handleCodeChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { const handleCodeChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const newCode = event.target.value; const newCode = event.target.value;
setEmbedCode(newCode); const trimmedCode = newCode.trim();
props.updateAttributes({ // Only update if code is not just whitespace
embedCode: newCode, if (newCode === '' || trimmedCode) {
embedType: 'code', setEmbedCode(newCode);
}); props.updateAttributes({
embedCode: newCode,
embedType: 'code',
});
}
}; };
// Add refs for storing dimensions during resize
const dimensionsRef = useRef({
width: props.node.attrs.embedWidth || '100%',
height: props.node.attrs.embedHeight || 300
})
const handleResizeStart = (event: React.MouseEvent<HTMLDivElement>, direction: 'horizontal' | 'vertical') => { const handleResizeStart = (event: React.MouseEvent<HTMLDivElement>, direction: 'horizontal' | 'vertical') => {
event.preventDefault() event.preventDefault()
setIsResizing(true)
const startX = event.clientX const startX = event.clientX
const startY = event.clientY const startY = event.clientY
const startWidth = resizeRef.current?.offsetWidth || 0 const startWidth = resizeRef.current?.offsetWidth || 0
@ -85,17 +155,29 @@ function EmbedObjectsComponent(props: any) {
const parentWidth = resizeRef.current.parentElement?.offsetWidth || 1 const parentWidth = resizeRef.current.parentElement?.offsetWidth || 1
const widthPercentage = Math.min(100, Math.max(10, (newWidth / parentWidth) * 100)) const widthPercentage = Math.min(100, Math.max(10, (newWidth / parentWidth) * 100))
const newWidthValue = `${widthPercentage}%` const newWidthValue = `${widthPercentage}%`
setEmbedWidth(newWidthValue)
props.updateAttributes({ embedWidth: newWidthValue }) // Update ref and DOM directly during resize
dimensionsRef.current.width = newWidthValue
resizeRef.current.style.width = newWidthValue
} else { } else {
const newHeight = Math.max(100, startHeight + e.clientY - startY) const newHeight = Math.max(100, startHeight + e.clientY - startY)
setEmbedHeight(newHeight)
props.updateAttributes({ embedHeight: newHeight }) // Update ref and DOM directly during resize
dimensionsRef.current.height = newHeight
resizeRef.current.style.height = `${newHeight}px`
} }
} }
} }
const handleMouseUp = () => { const handleMouseUp = () => {
setIsResizing(false)
// Only update state and attributes after resize is complete
setEmbedWidth(dimensionsRef.current.width)
setEmbedHeight(dimensionsRef.current.height)
props.updateAttributes({
embedWidth: dimensionsRef.current.width,
embedHeight: dimensionsRef.current.height
})
document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp) document.removeEventListener('mouseup', handleMouseUp)
} }
@ -114,6 +196,19 @@ function EmbedObjectsComponent(props: any) {
window.open(guide, '_blank', 'noopener,noreferrer') window.open(guide, '_blank', 'noopener,noreferrer')
} }
// Memoize the embed content
const embedContent = useMemo(() => (
!isResizing && (embedUrl || sanitizedEmbedCode) ? (
<MemoizedEmbed
embedUrl={embedUrl}
sanitizedEmbedCode={sanitizedEmbedCode}
embedType={embedType}
/>
) : (
<div className="w-full h-full bg-gray-200" />
)
), [embedUrl, sanitizedEmbedCode, embedType, isResizing]);
return ( return (
<NodeViewWrapper className="embed-block"> <NodeViewWrapper className="embed-block">
<div <div
@ -121,16 +216,7 @@ function EmbedObjectsComponent(props: any) {
className={`relative bg-gray-100 rounded-lg overflow-hidden flex justify-center items-center ${alignment === 'center' ? 'mx-auto' : ''}`} className={`relative bg-gray-100 rounded-lg overflow-hidden flex justify-center items-center ${alignment === 'center' ? 'mx-auto' : ''}`}
style={{ height: `${embedHeight}px`, width: embedWidth, minWidth: '400px' }} style={{ height: `${embedHeight}px`, width: embedWidth, minWidth: '400px' }}
> >
{embedType === 'url' && embedUrl ? ( {(embedUrl || sanitizedEmbedCode) ? embedContent : (
<iframe
src={embedUrl}
className="w-full h-full"
frameBorder="0"
allowFullScreen
/>
) : embedType === 'code' && sanitizedEmbedCode ? (
<div dangerouslySetInnerHTML={{ __html: sanitizedEmbedCode }} className="w-full h-full" />
) : (
<div className="w-full h-full flex flex-col items-center justify-center p-6"> <div className="w-full h-full flex flex-col items-center justify-center p-6">
<p className="text-gray-500 mb-4 font-medium tracking-tighter text-lg">Add an embed from :</p> <p className="text-gray-500 mb-4 font-medium tracking-tighter text-lg">Add an embed from :</p>
<div className="flex flex-wrap gap-5 justify-center"> <div className="flex flex-wrap gap-5 justify-center">
@ -210,3 +296,4 @@ function EmbedObjectsComponent(props: any) {
} }
export default EmbedObjectsComponent export default EmbedObjectsComponent

View file

@ -9,19 +9,8 @@ export const NoTextInput = Extension.create({
new Plugin({ new Plugin({
key: new PluginKey('noTextInput'), key: new PluginKey('noTextInput'),
filterTransaction: (transaction) => { filterTransaction: (transaction) => {
// If the transaction is adding text, stop it // Block all content-changing transactions
return ( return !transaction.docChanged
!transaction.docChanged ||
transaction.steps.every((step) => {
const { slice } = step.toJSON()
return (
!slice ||
!slice.content.some(
(node: { type: string }) => node.type === 'text'
)
)
})
)
}, },
}), }),
] ]

View file

@ -291,7 +291,7 @@ function QuizBlockComponent(props: any) {
<div <div
key={answer.answer_id} key={answer.answer_id}
className={twMerge( className={twMerge(
'outline outline-3 pr-2 shadow w-full flex items-center space-x-2 h-[30px] bg-opacity-50 hover:bg-opacity-100 hover:shadow-md rounded-s rounded-lg bg-white text-sm hover:scale-105 active:scale-110 duration-150 cursor-pointer ease-linear', 'outline outline-3 pr-2 shadow w-full flex items-center space-x-2 h-[30px] bg-opacity-50 hover:bg-opacity-100 hover:shadow-md rounded-s rounded-lg bg-white text-sm duration-150 cursor-pointer ease-linear',
answer.correct && isEditable ? 'outline-lime-300' : 'outline-white', answer.correct && isEditable ? 'outline-lime-300' : 'outline-white',
userAnswers.some( userAnswers.some(
(userAnswer: any) => (userAnswer: any) =>

View file

@ -4,6 +4,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext';
import useAdminStatus from '@components/Hooks/useAdminStatus'; import useAdminStatus from '@components/Hooks/useAdminStatus';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import PageLoading from '@components/Objects/Loaders/PageLoading'; import PageLoading from '@components/Objects/Loaders/PageLoading';
import { getUriWithoutOrg } from '@services/config/config';
import { useOrg } from '@components/Contexts/OrgContext';
type AuthorizationProps = { type AuthorizationProps = {
children: React.ReactNode; children: React.ReactNode;
@ -22,6 +24,7 @@ const ADMIN_PATHS = [
const AdminAuthorization: React.FC<AuthorizationProps> = ({ children, authorizationMode }) => { const AdminAuthorization: React.FC<AuthorizationProps> = ({ children, authorizationMode }) => {
const session = useLHSession() as any; const session = useLHSession() as any;
const org = useOrg() as any;
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
const { isAdmin, loading } = useAdminStatus() as any const { isAdmin, loading } = useAdminStatus() as any
@ -51,7 +54,7 @@ const AdminAuthorization: React.FC<AuthorizationProps> = ({ children, authorizat
} }
if (!isUserAuthenticated) { if (!isUserAuthenticated) {
router.push('/login'); router.push(getUriWithoutOrg('/login?orgslug=' + org.slug));
return; return;
} }

View file

@ -25,10 +25,10 @@ export const HeaderProfileBox = () => {
<ul className="flex space-x-3 items-center"> <ul className="flex space-x-3 items-center">
<li> <li>
<Link <Link
href={{ pathname: getUriWithoutOrg('/login'), query: org ? { orgslug: org.slug } : null }} >Login</Link> href={{ pathname: getUriWithoutOrg('/login?orgslug=' + org.slug), query: org ? { orgslug: org.slug } : null }} >Login</Link>
</li> </li>
<li className="bg-black rounded-lg shadow-md p-2 px-3 text-white"> <li className="bg-black rounded-lg shadow-md p-2 px-3 text-white">
<Link href={{ pathname: getUriWithoutOrg('/signup'), query: org ? { orgslug: org.slug } : null }}>Sign up</Link> <Link href={{ pathname: getUriWithoutOrg('/signup?orgslug=' + org.slug), query: org ? { orgslug: org.slug } : null }}>Sign up</Link>
</li> </li>
</ul> </ul>
</UnidentifiedArea> </UnidentifiedArea>