mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #400 from learnhouse/fix/misc-bugs
Fix Misc Bugs & Issues
This commit is contained in:
commit
8c7b4a3f7b
17 changed files with 214 additions and 92 deletions
|
|
@ -67,3 +67,33 @@ def migrate_v0_to_v1(v0_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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from sqlmodel import Session, select
|
||||
from config.config import get_learnhouse_config
|
||||
from migrations.orgconfigs.v0tov1 import migrate_v0_to_v1
|
||||
from migrations.orgconfigs.v1_1 import migrate_to_v1_1
|
||||
from migrations.orgconfigs.orgconfigs_migrations import migrate_to_v1_1, migrate_to_v1_2, migrate_v0_to_v1
|
||||
from src.core.events.database import get_db_session
|
||||
from src.db.organization_config import OrganizationConfig
|
||||
|
||||
|
|
@ -52,3 +51,21 @@ async def migratev1_1(
|
|||
db_session.commit()
|
||||
|
||||
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"}
|
||||
|
|
@ -16,7 +16,7 @@ from src.db.usergroup_resources import UserGroupResource
|
|||
from src.db.usergroup_user import UserGroupUser
|
||||
from src.db.organizations import Organization
|
||||
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(
|
||||
|
|
@ -275,7 +275,7 @@ async def delete_usergroup_by_id(
|
|||
async def add_users_to_usergroup(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
current_user: PublicUser | AnonymousUser | InternalUser,
|
||||
usergroup_id: int,
|
||||
user_ids: str,
|
||||
) -> str:
|
||||
|
|
@ -486,10 +486,13 @@ async def remove_resources_from_usergroup(
|
|||
async def rbac_check(
|
||||
request: Request,
|
||||
usergroup_uuid: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
current_user: PublicUser | AnonymousUser | InternalUser,
|
||||
action: Literal["create", "read", "update", "delete"],
|
||||
db_session: Session,
|
||||
):
|
||||
if isinstance(current_user, InternalUser):
|
||||
return True
|
||||
|
||||
await authorization_verify_if_user_is_anon(current_user.id)
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from src.security.rbac.rbac import (
|
|||
from src.db.organizations import Organization, OrganizationRead
|
||||
from src.db.users import (
|
||||
AnonymousUser,
|
||||
InternalUser,
|
||||
PublicUser,
|
||||
User,
|
||||
UserCreate,
|
||||
|
|
@ -147,19 +148,21 @@ async def create_user_with_invite(
|
|||
# Usage check
|
||||
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
|
||||
if inviteCode.usergroup_id:
|
||||
if inviteCode.get("usergroup_id"):
|
||||
# Add user to UserGroup
|
||||
await add_users_to_usergroup(
|
||||
request,
|
||||
db_session,
|
||||
current_user,
|
||||
inviteCode.usergroup_id,
|
||||
user_object.username,
|
||||
InternalUser(id=0),
|
||||
int(inviteCode.get("usergroup_id")), # Convert to int since usergroup_id is expected to be int
|
||||
str(user.id),
|
||||
)
|
||||
|
||||
user = await create_user(request, db_session, current_user, user_object, org_id)
|
||||
|
||||
increase_feature_usage("members", org_id, db_session)
|
||||
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import learnhouseIcon from 'public/learnhouse_bigicon_1.png'
|
|||
import Image from 'next/image'
|
||||
import { getOrgLogoMediaDirectory } from '@services/media/media'
|
||||
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 React, { useEffect } from '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"
|
||||
)
|
||||
setTimeout(() => {
|
||||
router.push(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`)
|
||||
router.push(getUriWithoutOrg(`/signup?inviteCode=${inviteCode}&orgslug=${org.slug}`))
|
||||
}, 2000)
|
||||
} else {
|
||||
toast.error('Invite code is invalid')
|
||||
|
|
|
|||
|
|
@ -74,8 +74,12 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
|||
|
||||
const removeOption = (qIndex: number, oIndex: number) => {
|
||||
const updatedQuestions = [...questions];
|
||||
updatedQuestions[qIndex].options.splice(oIndex, 1);
|
||||
setQuestions(updatedQuestions);
|
||||
if (updatedQuestions[qIndex].options.length > 1) {
|
||||
updatedQuestions[qIndex].options.splice(oIndex, 1);
|
||||
setQuestions(updatedQuestions);
|
||||
} else {
|
||||
toast.error('Cannot delete the last option. At least one option is required.');
|
||||
}
|
||||
};
|
||||
|
||||
const addQuestion = () => {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ function EditCourseGeneral(props: EditCourseStructureProps) {
|
|||
about: courseStructure?.about || '',
|
||||
learnings: courseStructure?.learnings || '',
|
||||
tags: courseStructure?.tags || '',
|
||||
public: courseStructure?.public || '',
|
||||
public: courseStructure?.public || false,
|
||||
},
|
||||
validate,
|
||||
onSubmit: async values => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
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 { Globe, Ticket, UserSquare, Users, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
|
@ -173,14 +173,12 @@ function OrgAccess() {
|
|||
<Link
|
||||
className="outline bg-gray-50 text-gray-600 px-2 py-1 rounded-md outline-gray-300 outline-dashed outline-1"
|
||||
target="_blank"
|
||||
href={getUriWithOrg(
|
||||
org?.slug,
|
||||
`/signup?inviteCode=${invite.invite_code}`
|
||||
href={getUriWithoutOrg(
|
||||
`/signup?inviteCode=${invite.invite_code}&orgslug=${org.slug}`
|
||||
)}
|
||||
>
|
||||
{getUriWithOrg(
|
||||
org?.slug,
|
||||
`/signup?inviteCode=${invite.invite_code}`
|
||||
{getUriWithoutOrg(
|
||||
`/signup?inviteCode=${invite.invite_code}&orgslug=${org.slug}`
|
||||
)}
|
||||
</Link>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -163,7 +163,9 @@ function AIActionButton(props: {
|
|||
})
|
||||
await dispatchAIChatBot({ type: 'setIsWaitingForResponse' })
|
||||
const response = await startActivityAIChatSession(
|
||||
message, access_token
|
||||
message,
|
||||
access_token,
|
||||
props.activity.activity_uuid
|
||||
)
|
||||
if (response.success == false) {
|
||||
await dispatchAIChatBot({ type: 'setIsNoLongerWaitingForResponse' })
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { revalidateTags } from '@services/utils/ts/requests'
|
|||
import { useRouter } from 'next/navigation'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
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 { LogIn, LogOut, ShoppingCart, AlertCircle } from 'lucide-react'
|
||||
import Modal from '@components/Objects/StyledElements/Modal/Modal'
|
||||
|
|
@ -126,7 +126,7 @@ const Actions = ({ courseuuid, orgslug, course }: CourseActionsProps) => {
|
|||
|
||||
const handleCourseAction = async () => {
|
||||
if (!session.data?.user) {
|
||||
router.push(getUriWithOrg(orgslug, '/signup?orgslug=' + orgslug))
|
||||
router.push(getUriWithoutOrg(`/signup?orgslug=${orgslug}`))
|
||||
return
|
||||
}
|
||||
const action = isStarted ? removeCourse : startCourse
|
||||
|
|
|
|||
|
|
@ -135,13 +135,11 @@ const BadgesExtension: React.FC = (props: any) => {
|
|||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<div className='flex space-x-2 items-center'>
|
||||
<div
|
||||
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',
|
||||
getBadgeColor(color)
|
||||
)}
|
||||
>
|
||||
<div className='flex space-x-2 items-center relative'>
|
||||
<div 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',
|
||||
getBadgeColor(color)
|
||||
)}>
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<span className='text'>{emoji}</span>
|
||||
{isEditable && (
|
||||
|
|
@ -176,6 +174,7 @@ const BadgesExtension: React.FC = (props: any) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditable && (
|
||||
<button
|
||||
onClick={() => setShowPredefinedCallouts(!showPredefinedCallouts)}
|
||||
|
|
@ -184,8 +183,9 @@ const BadgesExtension: React.FC = (props: any) => {
|
|||
<ChevronRight size={16} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{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) => (
|
||||
<button
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,66 @@
|
|||
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 { 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 { useRouter } from 'next/navigation'
|
||||
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) {
|
||||
const [embedType, setEmbedType] = useState<'url' | 'code'>(props.node.attrs.embedType || 'url')
|
||||
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 [embedWidth, setEmbedWidth] = useState(props.node.attrs.embedWidth || '100%')
|
||||
const [alignment, setAlignment] = useState(props.node.attrs.alignment || 'left')
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
|
||||
const resizeRef = useRef<HTMLDivElement>(null)
|
||||
const editorState = useEditorProvider() as any
|
||||
|
|
@ -53,26 +109,40 @@ function EmbedObjectsComponent(props: any) {
|
|||
|
||||
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newUrl = event.target.value;
|
||||
// Sanitize the URL
|
||||
const sanitizedUrl = DOMPurify.sanitize(newUrl);
|
||||
setEmbedUrl(sanitizedUrl);
|
||||
props.updateAttributes({
|
||||
embedUrl: sanitizedUrl,
|
||||
embedType: 'url',
|
||||
});
|
||||
const trimmedUrl = newUrl.trim();
|
||||
// Only update if URL is not just whitespace
|
||||
if (newUrl === '' || trimmedUrl) {
|
||||
const sanitizedUrl = DOMPurify.sanitize(newUrl);
|
||||
setEmbedUrl(sanitizedUrl);
|
||||
props.updateAttributes({
|
||||
embedUrl: sanitizedUrl,
|
||||
embedType: 'url',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCodeChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newCode = event.target.value;
|
||||
setEmbedCode(newCode);
|
||||
props.updateAttributes({
|
||||
embedCode: newCode,
|
||||
embedType: 'code',
|
||||
});
|
||||
const trimmedCode = newCode.trim();
|
||||
// Only update if code is not just whitespace
|
||||
if (newCode === '' || trimmedCode) {
|
||||
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') => {
|
||||
event.preventDefault()
|
||||
setIsResizing(true)
|
||||
const startX = event.clientX
|
||||
const startY = event.clientY
|
||||
const startWidth = resizeRef.current?.offsetWidth || 0
|
||||
|
|
@ -85,17 +155,29 @@ function EmbedObjectsComponent(props: any) {
|
|||
const parentWidth = resizeRef.current.parentElement?.offsetWidth || 1
|
||||
const widthPercentage = Math.min(100, Math.max(10, (newWidth / parentWidth) * 100))
|
||||
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 {
|
||||
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 = () => {
|
||||
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('mouseup', handleMouseUp)
|
||||
}
|
||||
|
|
@ -114,6 +196,19 @@ function EmbedObjectsComponent(props: any) {
|
|||
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 (
|
||||
<NodeViewWrapper className="embed-block">
|
||||
<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' : ''}`}
|
||||
style={{ height: `${embedHeight}px`, width: embedWidth, minWidth: '400px' }}
|
||||
>
|
||||
{embedType === 'url' && embedUrl ? (
|
||||
<iframe
|
||||
src={embedUrl}
|
||||
className="w-full h-full"
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
/>
|
||||
) : embedType === 'code' && sanitizedEmbedCode ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: sanitizedEmbedCode }} className="w-full h-full" />
|
||||
) : (
|
||||
{(embedUrl || sanitizedEmbedCode) ? embedContent : (
|
||||
<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>
|
||||
<div className="flex flex-wrap gap-5 justify-center">
|
||||
|
|
@ -210,3 +296,4 @@ function EmbedObjectsComponent(props: any) {
|
|||
}
|
||||
|
||||
export default EmbedObjectsComponent
|
||||
|
||||
|
|
|
|||
|
|
@ -9,19 +9,8 @@ export const NoTextInput = Extension.create({
|
|||
new Plugin({
|
||||
key: new PluginKey('noTextInput'),
|
||||
filterTransaction: (transaction) => {
|
||||
// If the transaction is adding text, stop it
|
||||
return (
|
||||
!transaction.docChanged ||
|
||||
transaction.steps.every((step) => {
|
||||
const { slice } = step.toJSON()
|
||||
return (
|
||||
!slice ||
|
||||
!slice.content.some(
|
||||
(node: { type: string }) => node.type === 'text'
|
||||
)
|
||||
)
|
||||
})
|
||||
)
|
||||
// Block all content-changing transactions
|
||||
return !transaction.docChanged
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ function QuizBlockComponent(props: any) {
|
|||
<div
|
||||
key={answer.answer_id}
|
||||
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',
|
||||
userAnswers.some(
|
||||
(userAnswer: any) =>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext';
|
|||
import useAdminStatus from '@components/Hooks/useAdminStatus';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import { getUriWithoutOrg } from '@services/config/config';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
|
||||
type AuthorizationProps = {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -22,6 +24,7 @@ const ADMIN_PATHS = [
|
|||
|
||||
const AdminAuthorization: React.FC<AuthorizationProps> = ({ children, authorizationMode }) => {
|
||||
const session = useLHSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { isAdmin, loading } = useAdminStatus() as any
|
||||
|
|
@ -51,7 +54,7 @@ const AdminAuthorization: React.FC<AuthorizationProps> = ({ children, authorizat
|
|||
}
|
||||
|
||||
if (!isUserAuthenticated) {
|
||||
router.push('/login');
|
||||
router.push(getUriWithoutOrg('/login?orgslug=' + org.slug));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ export const HeaderProfileBox = () => {
|
|||
<ul className="flex space-x-3 items-center">
|
||||
<li>
|
||||
<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 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>
|
||||
</ul>
|
||||
</UnidentifiedArea>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue