Merge branch 'fe/designs' into 'frontend'
Fe/designs See merge request s11-webmobile1-sub2/S11P12A701!56
This commit is contained in:
commit
16a0c8740d
8
frontend/src/assets/icons/compass.svg
Normal file
8
frontend/src/assets/icons/compass.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg width="49" height="49" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Compass">
|
||||||
|
<g id="Icon">
|
||||||
|
<path d="M24.5 44.5C35.5457 44.5 44.5 35.5457 44.5 24.5C44.5 13.4543 35.5457 4.5 24.5 4.5C13.4543 4.5 4.5 13.4543 4.5 24.5C4.5 35.5457 13.4543 44.5 24.5 44.5Z" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32.98 16.02L28.74 28.74L16.02 32.98L20.26 20.26L32.98 16.02Z" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 510 B |
5
frontend/src/assets/icons/plus.svg
Normal file
5
frontend/src/assets/icons/plus.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="49" height="49" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Plus circle">
|
||||||
|
<path id="Icon" d="M24.5 16.5V32.5M16.5 24.5H32.5M44.5 24.5C44.5 35.5457 35.5457 44.5 24.5 44.5C13.4543 44.5 4.5 35.5457 4.5 24.5C4.5 13.4543 13.4543 4.5 24.5 4.5C35.5457 4.5 44.5 13.4543 44.5 24.5Z" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 395 B |
@ -1,5 +1,6 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styles from './ClassCard.module.css';
|
import styles from './ClassCard.module.css';
|
||||||
|
import CompassIcon from '/src/assets/icons/compass.svg?react';
|
||||||
|
|
||||||
export default function ClassCard({ img, path, children }) {
|
export default function ClassCard({ img, path, children }) {
|
||||||
return (
|
return (
|
||||||
@ -7,11 +8,17 @@ export default function ClassCard({ img, path, children }) {
|
|||||||
to={path}
|
to={path}
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
>
|
>
|
||||||
|
{img ? (
|
||||||
<img
|
<img
|
||||||
src={img}
|
src={img}
|
||||||
alt="강의 이미지"
|
alt="강의 이미지"
|
||||||
className={styles.thumbnail}
|
className={styles.thumbnail}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={styles.thumbnail}>
|
||||||
|
<CompassIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -9,9 +9,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
width: 295px;
|
width: 295px;
|
||||||
height: 220px;
|
height: 220px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
|
stroke: var(--text-color);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,10 @@ export default function LiveRoom() {
|
|||||||
</FocusLayoutContainer>
|
</FocusLayoutContainer>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ControlBar controls={{ chat: false, leave: false, screenShare: role === '강사' }} />
|
<ControlBar
|
||||||
|
variation="minimal"
|
||||||
|
controls={{ chat: false, leave: true, screenShare: role === '강사' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ChatRoom />
|
<ChatRoom />
|
||||||
</LayoutContextProvider>
|
</LayoutContextProvider>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
.groupList {
|
.groupList {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 8px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -15,5 +15,4 @@
|
|||||||
|
|
||||||
.active {
|
.active {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import instance from '../../utils/axios/instance';
|
import instance from '../../utils/axios/instance';
|
||||||
import { API_URL } from '../../constants';
|
import { API_URL } from '../../constants';
|
||||||
|
|
||||||
export function useNotices(lectureId, page = 0) {
|
export function useNotices(lectureId, page = 0) {
|
||||||
return useQuery({
|
return useSuspenseQuery({
|
||||||
queryKey: ['noticelist', lectureId, page],
|
queryKey: ['noticelist', lectureId, page],
|
||||||
queryFn: () => instance.get(`${API_URL}/board?lectureId=${lectureId}&category=announcement&pageNo=${page}`),
|
queryFn: () => instance.get(`${API_URL}/board?lectureId=${lectureId}&category=announcement&pageNo=${page}`),
|
||||||
});
|
});
|
||||||
|
@ -1,31 +1,29 @@
|
|||||||
import { LiveRoom } from '../../components/LiveRoom';
|
import { LiveRoom } from '../../components/LiveRoom';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { LiveKitRoom } from '@livekit/components-react';
|
import { LiveKitRoom } from '@livekit/components-react';
|
||||||
import instance from '../../utils/axios/instance';
|
import instance from '../../utils/axios/instance';
|
||||||
import { API_URL, ROOM_URL } from '../../constants';
|
import { API_URL, ROOM_URL } from '../../constants';
|
||||||
import useBoundStore from '../../store';
|
|
||||||
import '@livekit/components-styles';
|
import '@livekit/components-styles';
|
||||||
import LoadingIndicator from '../../components/LoadingIndicator.jsx/LoadingIndicator';
|
import LoadingIndicator from '../../components/LoadingIndicator.jsx/LoadingIndicator';
|
||||||
|
|
||||||
export default function LivePage() {
|
export default function LivePage() {
|
||||||
const { roomId } = useParams();
|
const { roomId } = useParams();
|
||||||
|
const [liveToken, setLiveToken] = useState(null);
|
||||||
const generateToken = useCallback(async () => {
|
const generateToken = useCallback(async () => {
|
||||||
await instance.post(`${API_URL}/video/makeroom/${roomId}`);
|
await instance.post(`${API_URL}/video/makeroom/${roomId}`);
|
||||||
const { data } = await instance.post(`${API_URL}/video/joinroom/${roomId}`);
|
const { data } = await instance.post(`${API_URL}/video/joinroom/${roomId}`).catch(() => {
|
||||||
|
alert('방에 입장할 수 없습니다.');
|
||||||
|
});
|
||||||
|
|
||||||
return data.token;
|
return data.token;
|
||||||
}, [roomId]);
|
}, [roomId]);
|
||||||
|
|
||||||
const liveToken = useBoundStore((state) => state.liveToken);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!liveToken) {
|
|
||||||
generateToken().then((token) => {
|
generateToken().then((token) => {
|
||||||
useBoundStore.setState({ liveToken: token });
|
setLiveToken(token);
|
||||||
});
|
});
|
||||||
}
|
}, [generateToken]);
|
||||||
}, [generateToken, liveToken]);
|
|
||||||
|
|
||||||
return liveToken ? (
|
return liveToken ? (
|
||||||
<LiveKitRoom
|
<LiveKitRoom
|
||||||
@ -33,6 +31,9 @@ export default function LivePage() {
|
|||||||
serverUrl={ROOM_URL}
|
serverUrl={ROOM_URL}
|
||||||
connect={true}
|
connect={true}
|
||||||
data-lk-theme="default"
|
data-lk-theme="default"
|
||||||
|
onDisconnected={() => {
|
||||||
|
instance.post(`${API_URL}/video/deleteroom/${roomId}`).catch(() => {});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LiveRoom />
|
<LiveRoom />
|
||||||
</LiveKitRoom>
|
</LiveKitRoom>
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
|
import styles from './TeacherHomePage.module.css';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { ClassCard } from '../../components/ClassCard';
|
import { ClassCard } from '../../components/ClassCard';
|
||||||
import { ClassGrid } from '../../components/ClassGrid';
|
import { ClassGrid } from '../../components/ClassGrid';
|
||||||
import { MaxWidthLayout } from '../../components/Layout';
|
import { MaxWidthLayout } from '../../components/Layout';
|
||||||
import { useMyLectures } from '../../hooks/api/useMyLectures';
|
import { useMyLectures } from '../../hooks/api/useMyLectures';
|
||||||
|
import PlusIcon from '/src/assets/icons/plus.svg?react';
|
||||||
|
|
||||||
export default function TeacherHomePage() {
|
export default function TeacherHomePage() {
|
||||||
const { data: myLectures } = useMyLectures();
|
const { data: myLectures } = useMyLectures();
|
||||||
const onGoingClasses = myLectures?.data ?? [];
|
const onGoingClasses = myLectures?.data ?? [];
|
||||||
console.log(onGoingClasses);
|
|
||||||
// TODO: 새 강의 만들기 스타일 추가, 추가 기능 필요시 추가
|
|
||||||
return (
|
return (
|
||||||
<MaxWidthLayout>
|
<MaxWidthLayout>
|
||||||
<ClassGrid title="내 강의">
|
<ClassGrid title="내 강의">
|
||||||
{onGoingClasses.map?.((lecture) => (
|
{onGoingClasses.map((lecture) => (
|
||||||
<ClassCard
|
<ClassCard
|
||||||
key={lecture.id}
|
key={lecture.id}
|
||||||
path={`/lecture/${lecture.id}`}
|
path={`/lecture/${lecture.id}`}
|
||||||
@ -20,7 +22,13 @@ export default function TeacherHomePage() {
|
|||||||
{lecture.title}
|
{lecture.title}
|
||||||
</ClassCard>
|
</ClassCard>
|
||||||
))}
|
))}
|
||||||
<ClassCard path={'/lecture/create'}>새 강의 만들기</ClassCard>
|
<Link
|
||||||
|
to={'/lecture/create'}
|
||||||
|
className={styles.add}
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
<span>새 강의 만들기</span>
|
||||||
|
</Link>
|
||||||
</ClassGrid>
|
</ClassGrid>
|
||||||
</MaxWidthLayout>
|
</MaxWidthLayout>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
.add {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text-color);
|
||||||
|
stroke: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import { userTypeSlice } from './userTypeSlice';
|
|||||||
import { tokenSlice } from './tokenSlice';
|
import { tokenSlice } from './tokenSlice';
|
||||||
import { userNameSlice } from './userNameSlice';
|
import { userNameSlice } from './userNameSlice';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { liveSlice } from './liveSlice';
|
|
||||||
|
|
||||||
const useBoundStore = create(
|
const useBoundStore = create(
|
||||||
persist(
|
persist(
|
||||||
@ -11,7 +10,6 @@ const useBoundStore = create(
|
|||||||
...userTypeSlice(...a),
|
...userTypeSlice(...a),
|
||||||
...tokenSlice(...a),
|
...tokenSlice(...a),
|
||||||
...userNameSlice(...a),
|
...userNameSlice(...a),
|
||||||
...liveSlice(...a),
|
|
||||||
}),
|
}),
|
||||||
{ name: 'bound-store' }
|
{ name: 'bound-store' }
|
||||||
)
|
)
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export const liveSlice = (set) => ({
|
|
||||||
liveToken: null,
|
|
||||||
setLiveToken: (liveToken) => set({ liveToken }),
|
|
||||||
participants: 0,
|
|
||||||
setParticipants: (participants) => set({ participants }),
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user