diff --git a/Dockerfile b/Dockerfile
index b06d11ac..becd6359 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Base image
-FROM python:3.12-slim-bookworm as base
+FROM python:3.12.3-slim-bookworm as base
# Install Nginx, curl, and build-essential
RUN apt update && apt install -y nginx curl build-essential \
diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile
index 5aa1d296..e385219f 100644
--- a/apps/api/Dockerfile
+++ b/apps/api/Dockerfile
@@ -1,5 +1,5 @@
#
-FROM python:3.12
+FROM python:3.12.3
# poetry
RUN pip install poetry
diff --git a/apps/web/app/auth/options.ts b/apps/web/app/auth/options.ts
index 04553622..b953fd6a 100644
--- a/apps/web/app/auth/options.ts
+++ b/apps/web/app/auth/options.ts
@@ -9,7 +9,7 @@ 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 isDevEnv = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : false
export const nextAuthOptions = {
debug: true,
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 93dcdc2d..2ae9dd48 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -4,6 +4,8 @@ import StyledComponentsRegistry from '../components/Utils/libs/styled-registry'
import { motion } from 'framer-motion'
import { SessionProvider } from 'next-auth/react'
import LHSessionProvider from '@components/Contexts/LHSessionContext'
+import { isDevEnv } from './auth/options'
+import Script from 'next/script'
export default function RootLayout({
children,
@@ -19,6 +21,7 @@ export default function RootLayout({
+ {isDevEnv ? '' : }
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx
index b562c0d2..2d5a44c7 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/trail/trail.tsx
@@ -13,7 +13,7 @@ import useSWR from 'swr'
function Trail(params: any) {
let orgslug = params.orgslug
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const org = useOrg() as any
const orgID = org?.id
const { data: trail, error: error } = useSWR(
diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx
index 42c7c9bc..11d7d5df 100644
--- a/apps/web/components/Contexts/CourseContext.tsx
+++ b/apps/web/components/Contexts/CourseContext.tsx
@@ -12,8 +12,7 @@ export function CourseProvider({ children, courseuuid }: any) {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
- const { data: courseStructureData, error } = useSWR(
- access_token ? `${getAPIUrl()}courses/${courseuuid}/meta` : null,
+ const { data: courseStructureData, error } = useSWR(`${getAPIUrl()}courses/${courseuuid}/meta`,
url => swrFetcher(url, access_token)
);
diff --git a/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx b/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx
index eb465d22..ce8ef647 100644
--- a/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx
+++ b/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx
@@ -15,7 +15,7 @@ import { updateUserAvatar } from '@services/users/users'
function UserEditGeneral() {
const session = useLHSession() as any;
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [localAvatar, setLocalAvatar] = React.useState(null) as any
const [isLoading, setIsLoading] = React.useState(false) as any
const [error, setError] = React.useState() as any
diff --git a/apps/web/components/Dashboard/UserAccount/UserEditPassword/UserEditPassword.tsx b/apps/web/components/Dashboard/UserAccount/UserEditPassword/UserEditPassword.tsx
index 3f8a80b4..6c7751a3 100644
--- a/apps/web/components/Dashboard/UserAccount/UserEditPassword/UserEditPassword.tsx
+++ b/apps/web/components/Dashboard/UserAccount/UserEditPassword/UserEditPassword.tsx
@@ -5,7 +5,7 @@ import React, { useEffect } from 'react'
function UserEditPassword() {
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const updatePasswordUI = async (values: any) => {
let user_id = session.data.user.id
diff --git a/apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx b/apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx
index fd5a86b6..0f8e071e 100644
--- a/apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx
+++ b/apps/web/components/Dashboard/Users/OrgAccess/OrgAccess.tsx
@@ -21,7 +21,7 @@ import { useLHSession } from '@components/Contexts/LHSessionContext'
function OrgAccess() {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const { data: invites } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/invites` : null,
(url) => swrFetcher(url, access_token)
diff --git a/apps/web/components/Dashboard/Users/OrgUserGroups/OrgUserGroups.tsx b/apps/web/components/Dashboard/Users/OrgUserGroups/OrgUserGroups.tsx
index 4d357dd4..a665d4c6 100644
--- a/apps/web/components/Dashboard/Users/OrgUserGroups/OrgUserGroups.tsx
+++ b/apps/web/components/Dashboard/Users/OrgUserGroups/OrgUserGroups.tsx
@@ -16,7 +16,7 @@ import useSWR, { mutate } from 'swr'
function OrgUserGroups() {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [userGroupManagementModal, setUserGroupManagementModal] = React.useState(false)
const [createUserGroupModal, setCreateUserGroupModal] = React.useState(false)
const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any
diff --git a/apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx b/apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
index 6048816d..c25e5d84 100644
--- a/apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
+++ b/apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
@@ -16,7 +16,7 @@ import useSWR, { mutate } from 'swr'
function OrgUsers() {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const { data: orgUsers } = useSWR(
org ? `${getAPIUrl()}orgs/${org?.id}/users` : null,
(url) => swrFetcher(url, access_token)
diff --git a/apps/web/components/Dashboard/Users/OrgUsersAdd/OrgUsersAdd.tsx b/apps/web/components/Dashboard/Users/OrgUsersAdd/OrgUsersAdd.tsx
index 08bac53b..1cd167d4 100644
--- a/apps/web/components/Dashboard/Users/OrgUsersAdd/OrgUsersAdd.tsx
+++ b/apps/web/components/Dashboard/Users/OrgUsersAdd/OrgUsersAdd.tsx
@@ -14,7 +14,7 @@ import useSWR, { mutate } from 'swr'
function OrgUsersAdd() {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [isLoading, setIsLoading] = React.useState(false)
const [invitedUsers, setInvitedUsers] = React.useState('');
const [selectedInviteCode, setSelectedInviteCode] = React.useState('');
diff --git a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx
index a0a00582..e0b3c454 100644
--- a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx
+++ b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx
@@ -75,7 +75,7 @@ type ActivityChatMessageBoxProps = {
function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
const dispatchAIChatBot = useAIChatBotDispatch() as any
diff --git a/apps/web/components/Objects/Activities/DynamicCanva/AI/AICanvaToolkit.tsx b/apps/web/components/Objects/Activities/DynamicCanva/AI/AICanvaToolkit.tsx
index 7792e1ee..67692c0c 100644
--- a/apps/web/components/Objects/Activities/DynamicCanva/AI/AICanvaToolkit.tsx
+++ b/apps/web/components/Objects/Activities/DynamicCanva/AI/AICanvaToolkit.tsx
@@ -94,7 +94,7 @@ function AIActionButton(props: {
activity: any
}) {
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const dispatchAIChatBot = useAIChatBotDispatch() as any
const aiChatBotState = useAIChatBot() as AIChatBotStateTypes
diff --git a/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx
index fd287fe0..4b12b60b 100644
--- a/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx
+++ b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx
@@ -143,7 +143,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
const dispatchAIEditor = useAIEditorDispatch() as any
const aiEditorState = useAIEditor() as AIEditorStateTypes
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const handleChange = async (event: React.ChangeEvent) => {
await dispatchAIEditor({
diff --git a/apps/web/components/Objects/Editor/EditorWrapper.tsx b/apps/web/components/Objects/Editor/EditorWrapper.tsx
index d6502d58..aa0c2364 100644
--- a/apps/web/components/Objects/Editor/EditorWrapper.tsx
+++ b/apps/web/components/Objects/Editor/EditorWrapper.tsx
@@ -25,7 +25,7 @@ interface EditorWrapperProps {
function EditorWrapper(props: EditorWrapperProps): JSX.Element {
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
// Define provider in the state
const [provider, setProvider] = React.useState(null);
const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string)
diff --git a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
index 1f1beca7..778802fe 100644
--- a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
+++ b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
@@ -16,7 +16,7 @@ function ImageBlockComponent(props: any) {
const course = useCourse() as any
const editorState = useEditorProvider() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const isEditable = editorState.isEditable
const [image, setImage] = React.useState(null)
diff --git a/apps/web/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx b/apps/web/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx
index c6c61fbe..05d1f1b8 100644
--- a/apps/web/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx
+++ b/apps/web/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx
@@ -14,7 +14,7 @@ function PDFBlockComponent(props: any) {
const org = useOrg() as any
const course = useCourse() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [pdf, setPDF] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(false)
const [blockObject, setblockObject] = React.useState(
diff --git a/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx b/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
index f90923bd..52bf5a07 100644
--- a/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
+++ b/apps/web/components/Objects/Editor/Extensions/Video/VideoBlockComponent.tsx
@@ -17,7 +17,7 @@ function VideoBlockComponents(props: any) {
const isEditable = editorState.isEditable
const [video, setVideo] = React.useState(null)
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [isLoading, setIsLoading] = React.useState(false)
const [blockObject, setblockObject] = React.useState(
props.node.attrs.blockObject
diff --git a/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx b/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx
index 6cefeeda..cc4b8470 100644
--- a/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx
+++ b/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx
@@ -19,7 +19,7 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
const course = useCourse() as any
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const courseStructure = course.courseStructure
const { data: usergroups } = useSWR(
diff --git a/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx b/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx
index 06834009..ce67f16a 100644
--- a/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx
+++ b/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx
@@ -15,7 +15,7 @@ type OrgInviteCodeGenerateProps = {
function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [usergroup_id, setUsergroup_id] = React.useState(0);
const { data: usergroups } = useSWR(
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
diff --git a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx
index df4677bd..9429cd08 100644
--- a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx
+++ b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx
@@ -23,7 +23,7 @@ type AddUserGroupProps = {
function AddUserGroup(props: AddUserGroupProps) {
const org = useOrg() as any;
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [userGroupName, setUserGroupName] = React.useState('')
const [userGroupDescription, setUserGroupDescription] = React.useState('')
const [isSubmitting, setIsSubmitting] = React.useState(false)
diff --git a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx
index 71eb8390..f47f9cf1 100644
--- a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx
+++ b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx
@@ -16,7 +16,7 @@ type ManageUsersProps = {
function ManageUsers(props: ManageUsersProps) {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const { data: OrgUsers } = useSWR(
org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
swrFetcher
diff --git a/apps/web/components/Objects/Modals/Dash/OrgUsers/RolesUpdate.tsx b/apps/web/components/Objects/Modals/Dash/OrgUsers/RolesUpdate.tsx
index 1712ab19..681f1d85 100644
--- a/apps/web/components/Objects/Modals/Dash/OrgUsers/RolesUpdate.tsx
+++ b/apps/web/components/Objects/Modals/Dash/OrgUsers/RolesUpdate.tsx
@@ -24,7 +24,7 @@ interface Props {
function RolesUpdate(props: Props) {
const org = useOrg() as any
const session = useLHSession() as any
- const access_token = session.data.tokens.access_token;
+ const access_token = session?.data?.tokens?.access_token;
const [isSubmitting, setIsSubmitting] = React.useState(false)
const [assignedRole, setAssignedRole] = React.useState(
props.alreadyAssignedRole
diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts
index 2234ca51..829a905a 100644
--- a/apps/web/middleware.ts
+++ b/apps/web/middleware.ts
@@ -16,10 +16,11 @@ export const config = {
* 1. /api routes
* 2. /_next (Next.js internals)
* 3. /fonts (inside /public)
+ * 4. Umami Analytics
* 4. /examples (inside /public)
* 5. all root files inside /public (e.g. /favicon.ico)
*/
- '/((?!api|_next|fonts|examples|[\\w-]+\\.\\w+).*)',
+ '/((?!api|_next|fonts|umami|examples|[\\w-]+\\.\\w+).*)',
],
}
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 5fc07052..fcdab469 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,8 +1,19 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- reactStrictMode: false,
- output: 'standalone',
+ async rewrites() {
+ return [
+ {
+ source: '/umami/script.js',
+ destination: `https://eu.umami.is/script.js`,
+ },
+ {
+ source: '/umami/api/send',
+ destination: `https://eu.umami.is/api/send`,
+ },
+ ]
+ },
+ reactStrictMode: false,
+ output: 'standalone',
}
module.exports = nextConfig
-