Skip to content

Commit

Permalink
Merge branch 'main' into deployment
Browse files Browse the repository at this point in the history
# Conflicts:
#	clients/core/src/LandingPage/LandingPage.tsx
#	clients/core/src/management/ManagementConsole.tsx
#	clients/core/src/network/course.ts
#	clients/core/src/network/queries/course.ts
#	clients/shared_library/network/course.ts
  • Loading branch information
niclasheun committed Dec 4, 2024
2 parents c4d1df1 + 84dff1e commit 59a1f2a
Show file tree
Hide file tree
Showing 32 changed files with 1,715 additions and 25 deletions.
6 changes: 6 additions & 0 deletions clients/core/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { CourseOverview } from './Course/CourseOverview'
import { TemplateRoutes } from './PhaseMapping/ExternalRouters/TemplateRoutes'
import { Application } from './Application/Application'
import { PhaseRouterMapping } from './PhaseMapping/PhaseRouterMapping'
import PrivacyPage from './LegalPages/Privacy'
import ImprintPage from './LegalPages/Imprint'
import AboutPage from './LegalPages/AboutPage'

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -26,6 +29,9 @@ export const App = (): JSX.Element => {
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Routes>
<Route path='/about' element={<AboutPage />} />
<Route path='/privacy' element={<PrivacyPage />} />
<Route path='/imprint' element={<ImprintPage />} />
<Route path='/' element={<LandingPage />} />
<Route path='/management' element={<ManagementRoot />} />
<Route path='/management/general' element={<ManagementRoot />} />
Expand Down
138 changes: 138 additions & 0 deletions clients/core/src/Course/AddingCourse/AddCourseAppearance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from 'react'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { useForm } from 'react-hook-form'
import {
CourseAppearanceFormValues,
courseAppearanceFormSchema,
} from '../../validations/courseAppearance'
import { zodResolver } from '@hookform/resolvers/zod'
import { IconSelector } from './components/IconSelector'
import DynamicIcon from '@/components/DynamicIcon'

const subtleColors = [
'bg-red-100',
'bg-yellow-100',
'bg-green-100',
'bg-blue-100',
'bg-indigo-100',
'bg-purple-100',
'bg-pink-100',
'bg-orange-100',
'bg-teal-100',
'bg-cyan-100',
]

interface AddCourseAppearanceProps {
onBack: () => void
onSubmit: (data: CourseAppearanceFormValues) => void
}

export const AddCourseAppearance: React.FC<AddCourseAppearanceProps> = ({ onBack, onSubmit }) => {
const form = useForm<CourseAppearanceFormValues>({
resolver: zodResolver(courseAppearanceFormSchema),
defaultValues: {
color: '',
icon: '',
},
})

const selectedColor = form.watch('color')
const selectedIcon = form.watch('icon')

return (
<Form {...form}>
<h3 className='text-lg font-semibold mb-2'>Preview</h3>
<div className='flex items-center space-x-4'>
<div
className={`
relative flex aspect-square size-12 items-center justify-center
after:absolute after:inset-0 after:rounded-lg after:border-2 after:border-primary
`}
>
<div
className={`
flex aspect-square items-center justify-center rounded-lg text-gray-800
size-12
${selectedColor || 'bg-gray-100'}
`}
>
<DynamicIcon name={selectedIcon || 'circle-help'} />
</div>
</div>
<span className='text-sm text-gray-600'>
This is how your course icon will appear in the sidebar. <br />
<b>Please note:</b> it will only be displayed while the course is active.
</span>
</div>

<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'>
<FormField
control={form.control}
name='color'
render={({ field }) => (
<FormItem>
<FormLabel>Select a Color</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder='Select a color' />
</SelectTrigger>
</FormControl>
<SelectContent>
{subtleColors.map((color) => (
<SelectItem key={color} value={color}>
<div className='flex items-center'>
<div className={`w-4 h-4 rounded mr-2 ${color}`}></div>
{color.split('-')[1]}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='icon'
render={({ field }) => (
<FormItem>
<FormLabel>Select an Icon</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder='Select an icon' />
</SelectTrigger>
</FormControl>
<IconSelector />
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className='flex justify-between space-x-2'>
<Button type='button' variant='outline' onClick={onBack}>
Back
</Button>
<Button type='submit'>Add Course</Button>
</div>
</form>
</Form>
)
}
132 changes: 132 additions & 0 deletions clients/core/src/Course/AddingCourse/AddCourseDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useCallback, useEffect } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { CourseFormValues } from '../../validations/course'
import { AddCourseProperties } from './AddCourseProperties'
import { AddCourseAppearance } from './AddCourseAppearance'
import { CourseAppearanceFormValues } from 'src/validations/courseAppearance'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { PostCourse } from '@/interfaces/post_course'
import { postNewCourse } from '../../network/mutations/postNewCourse'
import { useNavigate } from 'react-router-dom'
import { AlertCircle, Loader2 } from 'lucide-react'

interface AddCourseDialogProps {
children: React.ReactNode
}

export const AddCourseDialog: React.FC<AddCourseDialogProps> = ({ children }) => {
const [isOpen, setIsOpen] = React.useState(false)
const [currentPage, setCurrentPage] = React.useState(1)
const [coursePropertiesFormValues, setCoursePropertiesFormValues] =
React.useState<CourseFormValues | null>(null)

const queryClient = useQueryClient()
const navigate = useNavigate()

const { mutate, isPending, error, isError, reset } = useMutation({
mutationFn: (course: PostCourse) => {
return postNewCourse(course)
},
onSuccess: (data: string | undefined) => {
console.log('Received ID' + data)
queryClient.invalidateQueries({ queryKey: ['courses'] })

setIsOpen(false)
setIsOpen(false)
navigate(`/management/course/${data}`)
},
})

const onSubmit = (data: CourseAppearanceFormValues) => {
const course: PostCourse = {
name: coursePropertiesFormValues?.name || '',
start_date: coursePropertiesFormValues?.dateRange?.from || new Date(),
end_date: coursePropertiesFormValues?.dateRange?.to || new Date(),
course_type: coursePropertiesFormValues?.course_type || '',
ects: coursePropertiesFormValues?.ects || 0,
semester_tag: coursePropertiesFormValues?.semester_tag || '',
// eslint-disable-next-line prettier/prettier
meta_data: { icon: data.icon, 'bg-color': data.color },
}

console.log(course)
// todo API call
mutate(course)

setCurrentPage(1)
setCoursePropertiesFormValues(null) // reset the page 1 form
}

const handleNext = (data) => {
setCoursePropertiesFormValues(data)
setCurrentPage(2)
}

const handleBack = () => {
setCurrentPage(1)
}

const handleCancel = useCallback(() => {
setIsOpen(false)
}, [])

// makes sure that window is first closed, before resetting the form
useEffect(() => {
if (!isOpen) {
reset()
setCoursePropertiesFormValues(null)
setCurrentPage(1)
}
}, [isOpen, reset])

const handleOpenChange = (open: boolean) => {
if (!open) {
handleCancel()
} else {
setIsOpen(open)
}
}

return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className='sm:max-w-[550px]'>
{isPending ? (
<div className='flex flex-col items-center justify-center h-48'>
<Loader2 className='h-10 w-10 text-primary animate-spin' />
<p className='mt-4 text-lg font-medium text-muted-foreground'>Loading course data...</p>
</div>
) : isError ? (
<div className='flex flex-col items-center justify-center h-48'>
<AlertCircle className='h-10 w-10 text-destructive' />
<p className='mt-4 text-lg font-medium text-destructive'>Error: {error.message}</p>
<p className='mt-2 text-sm text-muted-foreground'>
Please try again or contact support if the problem persists.
</p>
</div>
) : (
<>
<DialogHeader>
<DialogTitle className='text-2xl font-bold text-center'>Add a New Course</DialogTitle>
</DialogHeader>
{currentPage === 1 ? (
<AddCourseProperties
onNext={handleNext}
onCancel={handleCancel}
initialValues={coursePropertiesFormValues || undefined}
/>
) : (
<AddCourseAppearance onBack={handleBack} onSubmit={onSubmit} />
)}
</>
)}
</DialogContent>
</Dialog>
)
}
Loading

0 comments on commit 59a1f2a

Please sign in to comment.