diff --git a/docs/spec/CodeCharacter-API.yml b/docs/spec/CodeCharacter-API.yml index 0e322f3..ed33ccf 100644 --- a/docs/spec/CodeCharacter-API.yml +++ b/docs/spec/CodeCharacter-API.yml @@ -699,6 +699,7 @@ paths: college: NITT avatarId: 1 tutorialLevel: 2 + codeTutorialLevel: 3 tier: TIER1 isProfileComplete: true isTutorialComplete: true @@ -1588,6 +1589,73 @@ paths: description: Gets all statistics for the current user parameters: [] + '/codetutorial/get/{codeTutorialNumber}': + get: + summary: Get tutorial by number + description: Get a single tutorial + operationId: getCodeTutorialByNumber + parameters: + - name: codeTutorialNumber + in: path + required: true + schema: + type: integer + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TutorialsGetRequest' + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: Not found + tags: + - Tutorials + + /codetutorial/submit: + post: + summary: Match Execution for Tutorials + description: Match making for Tutorials + operationId: createCodeTutorialMatch + responses: + '201': + description: Created + '400': + description: Bad Request + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + examples: + Example: + value: + message: Some field missing + '401': + description: Unauthorized + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CodeTutorialMatchRequest' + examples: + CodeExample: + value: + value: '#include' + language: CPP + codeTutorialNumber: 2 + MapExample: + value: + value: '[[0,0,0]]' + codeTutorialNumber: 6 + tags: + - Tutorials + components: schemas: UserMatchStats: @@ -2060,6 +2128,9 @@ components: tutorialLevel: type: integer example: 1 + codeTutorialLevel: + type: integer + example: 3 tier: $ref: '#/components/schemas/TierType' isProfileComplete: @@ -2077,6 +2148,7 @@ components: - college - avatarId - tutorialLevel + - codeTutorialLevel - isProfileComplete - isTutorialComplete UpdateCurrentUserProfile: @@ -2296,6 +2368,29 @@ components: - destruction - coinsUsed - status + TutorialGame: + title: Game + type: object + description: Game model + properties: + id: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426614174000 + destruction: + type: number + example: 100 + coinsUsed: + type: integer + logs: + type: string + status: + $ref: '#/components/schemas/GameStatus' + required: + - id + - destruction + - coinsUsed + - status GameLog: type: string description: Game log model @@ -2351,6 +2446,45 @@ components: required: - user - stats + TutorialsGetRequest: + title: Get tutorials + description: Get the game tutorials + type: object + properties: + tutorialId: + type: integer + example: 1 + tutorialName: + type: string + example: Tutorial for Spawning attacker + tutorialType: + $ref: '#/components/schemas/ChallengeType' + description: + type: string + example: Brief description of how the code works + tutorialCodes: + $ref: '#/components/schemas/TutorialCodeObject' + required: + - tutorialId + - tutorialName + - tutorialCodes + + TutorialCodeObject: + title: Tutorial Code Object + description: The object containing the code for the tutorial + type: object + properties: + cpp: + type: string + example: This would have the cpp code for the tutorial + java: + type: string + example: This would have the java code for the tutorial + python: + type: string + example: This would have the python code for the tutorial + image: + type: string DailyChallengeGetRequest: title: Get daily challenge @@ -2417,6 +2551,22 @@ components: $ref: '#/components/schemas/Language' required: - value + CodeTutorialMatchRequest: + title: CodeTutorialMatchRequest + description: Request Model for the tutorial mode + type: object + properties: + value: + type: string + example: '#include' + language: + $ref: '#/components/schemas/Language' + codeTutorialNumber: + type: integer + nullable: false + required: + - value + - codeTutorialNumber GenericError: title: GenericError type: object diff --git a/packages/client/.openapi-generator/FILES b/packages/client/.openapi-generator/FILES index ed24da6..9931ed7 100644 --- a/packages/client/.openapi-generator/FILES +++ b/packages/client/.openapi-generator/FILES @@ -10,6 +10,7 @@ src/apis/MatchApi.ts src/apis/NotificationApi.ts src/apis/PvpGameApi.ts src/apis/StatsApi.ts +src/apis/TutorialsApi.ts src/apis/UserApi.ts src/apis/index.ts src/index.ts diff --git a/packages/client/src/apis/TutorialsApi.ts b/packages/client/src/apis/TutorialsApi.ts new file mode 100644 index 0000000..c3abb59 --- /dev/null +++ b/packages/client/src/apis/TutorialsApi.ts @@ -0,0 +1,209 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * CodeCharacter API + * Specification of the CodeCharacter API + * + * The version of the OpenAPI document: 2024.0.1 + * Contact: delta@nitt.edu + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import * as runtime from '../runtime'; +import type { + CodeTutorialMatchRequest, + GenericError, + TutorialsGetRequest, +} from '../models'; + +export interface CreateCodeTutorialMatchRequest { + codeTutorialMatchRequest: CodeTutorialMatchRequest; +} + +export interface GetCodeTutorialByNumberRequest { + codeTutorialNumber: number; +} + +/** + * TutorialsApi - interface + * + * @export + * @interface TutorialsApiInterface + */ +export interface TutorialsApiInterface { + /** + * Match making for Tutorials + * @summary Match Execution for Tutorials + * @param {CodeTutorialMatchRequest} codeTutorialMatchRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TutorialsApiInterface + */ + createCodeTutorialMatchRaw( + requestParameters: CreateCodeTutorialMatchRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise>; + + /** + * Match making for Tutorials + * Match Execution for Tutorials + */ + createCodeTutorialMatch( + codeTutorialMatchRequest: CodeTutorialMatchRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise; + + /** + * Get a single tutorial + * @summary Get tutorial by number + * @param {number} codeTutorialNumber + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TutorialsApiInterface + */ + getCodeTutorialByNumberRaw( + requestParameters: GetCodeTutorialByNumberRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise>; + + /** + * Get a single tutorial + * Get tutorial by number + */ + getCodeTutorialByNumber( + codeTutorialNumber: number, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise; +} + +/** + * + */ +export class TutorialsApi + extends runtime.BaseAPI + implements TutorialsApiInterface +{ + /** + * Match making for Tutorials + * Match Execution for Tutorials + */ + async createCodeTutorialMatchRaw( + requestParameters: CreateCodeTutorialMatchRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.codeTutorialMatchRequest === null || + requestParameters.codeTutorialMatchRequest === undefined + ) { + throw new runtime.RequiredError( + 'codeTutorialMatchRequest', + 'Required parameter requestParameters.codeTutorialMatchRequest was null or undefined when calling createCodeTutorialMatch.', + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('http-bearer', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + const response = await this.request( + { + path: `/codetutorial/submit`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters.codeTutorialMatchRequest, + }, + initOverrides, + ); + + return new runtime.VoidApiResponse(response); + } + + /** + * Match making for Tutorials + * Match Execution for Tutorials + */ + async createCodeTutorialMatch( + codeTutorialMatchRequest: CodeTutorialMatchRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + await this.createCodeTutorialMatchRaw( + { codeTutorialMatchRequest: codeTutorialMatchRequest }, + initOverrides, + ); + } + + /** + * Get a single tutorial + * Get tutorial by number + */ + async getCodeTutorialByNumberRaw( + requestParameters: GetCodeTutorialByNumberRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.codeTutorialNumber === null || + requestParameters.codeTutorialNumber === undefined + ) { + throw new runtime.RequiredError( + 'codeTutorialNumber', + 'Required parameter requestParameters.codeTutorialNumber was null or undefined when calling getCodeTutorialByNumber.', + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('http-bearer', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + const response = await this.request( + { + path: `/codetutorial/get/{codeTutorialNumber}`.replace( + `{${'codeTutorialNumber'}}`, + encodeURIComponent(String(requestParameters.codeTutorialNumber)), + ), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + * Get a single tutorial + * Get tutorial by number + */ + async getCodeTutorialByNumber( + codeTutorialNumber: number, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.getCodeTutorialByNumberRaw( + { codeTutorialNumber: codeTutorialNumber }, + initOverrides, + ); + return await response.value(); + } +} diff --git a/packages/client/src/apis/index.ts b/packages/client/src/apis/index.ts index 71d785d..e99d3f8 100644 --- a/packages/client/src/apis/index.ts +++ b/packages/client/src/apis/index.ts @@ -11,4 +11,5 @@ export * from './MatchApi'; export * from './NotificationApi'; export * from './PvpGameApi'; export * from './StatsApi'; +export * from './TutorialsApi'; export * from './UserApi'; diff --git a/packages/client/src/models/index.ts b/packages/client/src/models/index.ts index 5fc4643..3fcd30f 100644 --- a/packages/client/src/models/index.ts +++ b/packages/client/src/models/index.ts @@ -122,6 +122,31 @@ export interface CodeRevision { */ codeType: CodeType; } +/** + * Request Model for the tutorial mode + * @export + * @interface CodeTutorialMatchRequest + */ +export interface CodeTutorialMatchRequest { + /** + * + * @type {string} + * @memberof CodeTutorialMatchRequest + */ + value: string; + /** + * + * @type {Language} + * @memberof CodeTutorialMatchRequest + */ + language?: Language; + /** + * + * @type {number} + * @memberof CodeTutorialMatchRequest + */ + codeTutorialNumber: number; +} /** * @@ -327,6 +352,12 @@ export interface CurrentUserProfile { * @memberof CurrentUserProfile */ tutorialLevel: number; + /** + * + * @type {number} + * @memberof CurrentUserProfile + */ + codeTutorialLevel: number; /** * * @type {TierType} @@ -1101,6 +1132,75 @@ export const TierType = { } as const; export type TierType = (typeof TierType)[keyof typeof TierType]; +/** + * The object containing the code for the tutorial + * @export + * @interface TutorialCodeObject + */ +export interface TutorialCodeObject { + /** + * + * @type {string} + * @memberof TutorialCodeObject + */ + cpp?: string; + /** + * + * @type {string} + * @memberof TutorialCodeObject + */ + java?: string; + /** + * + * @type {string} + * @memberof TutorialCodeObject + */ + python?: string; + /** + * + * @type {string} + * @memberof TutorialCodeObject + */ + image?: string; +} +/** + * Game model + * @export + * @interface TutorialGame + */ +export interface TutorialGame { + /** + * + * @type {string} + * @memberof TutorialGame + */ + id: string; + /** + * + * @type {number} + * @memberof TutorialGame + */ + destruction: number; + /** + * + * @type {number} + * @memberof TutorialGame + */ + coinsUsed: number; + /** + * + * @type {string} + * @memberof TutorialGame + */ + logs?: string; + /** + * + * @type {GameStatus} + * @memberof TutorialGame + */ + status: GameStatus; +} + /** * * @export @@ -1114,6 +1214,43 @@ export const TutorialUpdateType = { export type TutorialUpdateType = (typeof TutorialUpdateType)[keyof typeof TutorialUpdateType]; +/** + * Get the game tutorials + * @export + * @interface TutorialsGetRequest + */ +export interface TutorialsGetRequest { + /** + * + * @type {number} + * @memberof TutorialsGetRequest + */ + tutorialId: number; + /** + * + * @type {string} + * @memberof TutorialsGetRequest + */ + tutorialName: string; + /** + * + * @type {ChallengeType} + * @memberof TutorialsGetRequest + */ + tutorialType?: ChallengeType; + /** + * + * @type {string} + * @memberof TutorialsGetRequest + */ + description?: string; + /** + * + * @type {TutorialCodeObject} + * @memberof TutorialsGetRequest + */ + tutorialCodes: TutorialCodeObject; +} /** * Update current user profile request * @export diff --git a/src/assets/book (1).png:Zone.Identifier b/src/assets/book (1).png:Zone.Identifier new file mode 100644 index 0000000..053138f --- /dev/null +++ b/src/assets/book (1).png:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://www.flaticon.com/free-icon/book_13637888?term=code+tutorials&page=1&position=1&origin=search&related_id=13637888 +HostUrl=https://www.flaticon.com/download/icon/edited diff --git a/src/assets/tutorial.png b/src/assets/tutorial.png new file mode 100644 index 0000000..757b773 Binary files /dev/null and b/src/assets/tutorial.png differ diff --git a/src/components/BattleTV/BattleTV.tsx b/src/components/BattleTV/BattleTV.tsx index 7339b74..58b36b3 100644 --- a/src/components/BattleTV/BattleTV.tsx +++ b/src/components/BattleTV/BattleTV.tsx @@ -153,11 +153,11 @@ function PaginatedItems({ battleTvType }: { battleTvType: BattleType }) { ? match.game.scorePlayer1 : [...match.games.values()][0].coinsUsed} - - {'game' in match - ? '----' - : [...match.games.values()][0].destruction.toFixed(2)} - + {'games' in match && ( + + {[...match.games.values()][0].destruction.toFixed(2)} + + )}
{ @@ -192,13 +192,13 @@ function PaginatedItems({ battleTvType }: { battleTvType: BattleType }) { [...match.games.values()].length === 1 ? 0 : 1 ].destruction.toFixed(2)} - - {'game' in match - ? '----' - : [...match.games.values()][ - [...match.games.values()].length === 1 ? 0 : 1 - ].coinsUsed} - + {'games' in match && ( + + {[...match.games.values()][ + [...match.games.values()].length === 1 ? 0 : 1 + ].destruction.toFixed(2)} + + )} {match.user2 !== null diff --git a/src/components/DashboardOptions/DashboardOptions.tsx b/src/components/DashboardOptions/DashboardOptions.tsx index 7fdca90..b097f68 100644 --- a/src/components/DashboardOptions/DashboardOptions.tsx +++ b/src/components/DashboardOptions/DashboardOptions.tsx @@ -48,7 +48,7 @@ const DashboardOptions = (props: dashboardoptions): JSX.Element => { View Profile - Revisit Tutorial + Revisit Tour Logout diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index d20830c..e446088 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -55,8 +55,13 @@ import { dcCode, changeDcCode, } from '../../store/DailyChallenge/dailyChallenge'; +import { + changeTutorialCode, + tutorialCode, +} from '../../store/Tutorials/tutorials'; import { buildWorkerDefinition } from 'monaco-editor-workers'; import { Uri } from 'vscode'; +import toast from 'react-hot-toast'; buildWorkerDefinition( '../../node_modules/monaco-editor-workers/dist/workers', @@ -66,17 +71,17 @@ buildWorkerDefinition( export default function CodeEditor(props: Editor.Props): JSX.Element { const divCodeEditor = useRef(null); - const userCode: string = - props.page == 'Dashboard' - ? useAppSelector(UserCode) - : useAppSelector(dcCode); const fontSize: number = useAppSelector(FontSize); const theme: string = useAppSelector(Theme); const autocomplete: boolean = useAppSelector(Autocomplete); const dispatch: React.Dispatch = useAppDispatch(); - const keyboardHandler = useAppSelector(KeyboardHandler); - + const userCode: string = + props.page == 'Dashboard' + ? useAppSelector(UserCode) + : props.page == 'DailyChallenge' + ? useAppSelector(dcCode) + : useAppSelector(tutorialCode); const language = props.language; monaco.languages.register({ @@ -169,6 +174,8 @@ export default function CodeEditor(props: Editor.Props): JSX.Element { dispatch(updateUserCode(codeNlanguage)); } else if (props.page == 'DailyChallenge') { dispatch(changeDcCode(codeNlanguage)); + } else if (props.page == 'Tutorials') { + dispatch(changeTutorialCode(codeNlanguage)); } }); @@ -178,24 +185,26 @@ export default function CodeEditor(props: Editor.Props): JSX.Element { }); //Keybinding for Simulate -> CTRL+ALT+N - editor.addCommand( - monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyN, - function () { - if (GameType.NORMAL) { - dispatch(isSelfMatchModalOpened(true)); - dispatch(codeCommitNameChanged('Current Code')); - dispatch(codeCommitIDChanged(null)); - dispatch(mapCommitNameChanged('Current Map')); - dispatch(mapCommitIDChanged(null)); - } else if (GameType.PVP) { - dispatch(isPvPSelfMatchModalOpened(true)); - dispatch(code1CommitNameChanged('Current Code')); - dispatch(code1CommitIDChanged(null)); - dispatch(code2CommitNameChanged('Current Code')); - dispatch(code2CommitIDChanged(null)); - } - }, - ); + if (props.page == 'Dashboard' || props.page == 'Tutorials') { + editor.addCommand( + monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyN, + function () { + if (GameType.NORMAL) { + dispatch(isSelfMatchModalOpened(true)); + dispatch(codeCommitNameChanged('Current Code')); + dispatch(codeCommitIDChanged(null)); + dispatch(mapCommitNameChanged('Current Map')); + dispatch(mapCommitIDChanged(null)); + } else if (GameType.PVP) { + dispatch(isPvPSelfMatchModalOpened(true)); + dispatch(code1CommitNameChanged('Current Code')); + dispatch(code1CommitIDChanged(null)); + dispatch(code2CommitNameChanged('Current Code')); + dispatch(code2CommitIDChanged(null)); + } + }, + ); + } //Keybinding for Commit -> CTRL+K editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, function () { @@ -223,6 +232,14 @@ export default function CodeEditor(props: Editor.Props): JSX.Element { props.language == 'c_cpp' ? 'cpp' : props.language }`; wsClient = new WebSocket(url); + wsClient.onerror = () => { + toast.error('Error occured in lsp'); + editor = createEditor( + divCodeEditor.current as HTMLDivElement, + null, + null, + ); + }; wsClient.onopen = () => { const updater = { operation: 'fileUpdate', @@ -284,6 +301,7 @@ export default function CodeEditor(props: Editor.Props): JSX.Element { props.page, autocomplete, props.gameType, + props.tutorialNumber, ]); return
; diff --git a/src/components/Editor/EditorTypes.ts b/src/components/Editor/EditorTypes.ts index 84a659f..70d6157 100644 --- a/src/components/Editor/EditorTypes.ts +++ b/src/components/Editor/EditorTypes.ts @@ -5,6 +5,7 @@ interface PageType { Dashboard: 'Dashboard'; DailyChallenge: 'DailyChallenge'; PvP: 'PvP'; + Tutorials: 'Tutorials'; } export type Props = { @@ -13,6 +14,7 @@ export type Props = { SaveRef: RefObject; SubmitRef: RefObject; gameType: GameType; + tutorialNumber: number; }; export type Workspace = { diff --git a/src/components/EditorInfo/EditorInfo.tsx b/src/components/EditorInfo/EditorInfo.tsx index 2ae0a36..e3f3f50 100644 --- a/src/components/EditorInfo/EditorInfo.tsx +++ b/src/components/EditorInfo/EditorInfo.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { Col, Modal, Row } from 'react-bootstrap'; import { IsInfoOpen, isInfoOpened } from '../../store/EditorSettings/settings'; @@ -9,11 +10,14 @@ import { dailyChallengePageState, dcDescription, } from '../../store/DailyChallenge/dailyChallenge'; +import { + tutorialDescription, + tutorialCode, +} from '../../store/Tutorials/tutorials'; const EditorInfo = (): JSX.Element => { const homePageState = useAppSelector(dailyChallengePageState); const dailyChallengeDescription = useAppSelector(dcDescription); - const isInfoOpen = useAppSelector(IsInfoOpen); const dispatch = useAppDispatch(); @@ -26,7 +30,7 @@ const EditorInfo = (): JSX.Element => { onHide={() => dispatch(isInfoOpened(false))} > - {homePageState == 'Dashboard' || homePageState == 'PvP' ? ( + {homePageState == 'Dashboard' || homePageState == 'Tutorials' ? ( Editor Shortcuts @@ -44,7 +48,7 @@ const EditorInfo = (): JSX.Element => { - {homePageState == 'Dashboard' || homePageState == 'PvP' ? ( + {homePageState == 'Dashboard' || homePageState == 'Tutorials' ? ( {shortcuts.map((shortcut, index) => ( diff --git a/src/components/GraphToolTips/ToolTips.tsx b/src/components/GraphToolTips/ToolTips.tsx index 93a9a84..cb4ad68 100644 --- a/src/components/GraphToolTips/ToolTips.tsx +++ b/src/components/GraphToolTips/ToolTips.tsx @@ -1,7 +1,5 @@ import styles from './GraphToolTip.module.css'; export const BarChartToolTip = ({ active, payload }: ToolTipProps) => { - console.log(active); - console.log(payload); if (active && payload && payload.length) { return (
@@ -17,9 +15,6 @@ export const BarChartToolTip = ({ active, payload }: ToolTipProps) => { }; export const LineChartToolTip = ({ active, payload }: ToolTipProps) => { - console.log(active); - console.log(payload); - if (active && payload && payload.length) { return (
@@ -38,9 +33,6 @@ export const LineChartToolTip = ({ active, payload }: ToolTipProps) => { }; export const AreaChartToolTip = ({ active, payload }: ToolTipProps) => { - console.log(active); - console.log(payload); - if (active && payload && payload.length) { return (
@@ -61,9 +53,6 @@ export const AreaChartToolTip = ({ active, payload }: ToolTipProps) => { return null; }; export const DCToolTip = ({ active, payload }: ToolTipProps) => { - console.log(active); - console.log(payload); - if (active && payload && payload.length) { return (
diff --git a/src/components/MapDesigner/MapDesigner.tsx b/src/components/MapDesigner/MapDesigner.tsx index 1c884ec..4901e0d 100644 --- a/src/components/MapDesigner/MapDesigner.tsx +++ b/src/components/MapDesigner/MapDesigner.tsx @@ -7,6 +7,7 @@ import { MapApi, DailyChallengesApi, CurrentUserApi, + TutorialsApi, } from '@codecharacter-2024/client'; import Toast from 'react-hot-toast'; import styles from './MapDesigner.module.css'; @@ -15,7 +16,7 @@ import { Modal, Container, Row, Button } from 'react-bootstrap'; import { useTour } from '@reactour/tour'; interface MapDesignerProps { - pageType: 'MapDesigner' | 'DailyChallenge'; + pageType: 'MapDesigner' | 'DailyChallenge' | 'Tutorials'; } export default function MapDesigner(props: MapDesignerProps): JSX.Element { @@ -32,11 +33,14 @@ export default function MapDesigner(props: MapDesignerProps): JSX.Element { const mapAPI = new MapApi(apiConfig); const dcAPI = new DailyChallengesApi(apiConfig); const currentUserapi = new CurrentUserApi(apiConfig); + const tutorialAPI = new TutorialsApi(apiConfig); useEffect(() => { mapAPI .getLatestMap( - props.pageType == 'MapDesigner' ? 'NORMAL' : 'DAILY_CHALLENGE', + props.pageType == 'MapDesigner' || props.pageType == 'Tutorials' + ? 'NORMAL' + : 'DAILY_CHALLENGE', ) .then(mp => { setStagedMap(JSON.parse(mp.map)); @@ -102,7 +106,9 @@ export default function MapDesigner(props: MapDesignerProps): JSX.Element { mapAPI .updateLatestMap({ mapType: - props.pageType == 'MapDesigner' ? 'NORMAL' : 'DAILY_CHALLENGE', + props.pageType == 'MapDesigner' || props.pageType == 'Tutorials' + ? 'NORMAL' + : 'DAILY_CHALLENGE', mapImage: mapImg, map: JSON.stringify(stagedMap), lock: false, @@ -121,7 +127,9 @@ export default function MapDesigner(props: MapDesignerProps): JSX.Element { mapAPI .updateLatestMap({ mapType: - props.pageType == 'MapDesigner' ? 'NORMAL' : 'DAILY_CHALLENGE', + props.pageType == 'MapDesigner' || props.pageType == 'Tutorials' + ? 'NORMAL' + : 'DAILY_CHALLENGE', mapImage: mapImg, map: JSON.stringify(stagedMap), lock: true, @@ -150,6 +158,21 @@ export default function MapDesigner(props: MapDesignerProps): JSX.Element { } }); } + if (props.pageType == 'Tutorials') { + tutorialAPI + .createCodeTutorialMatch({ + value: JSON.stringify(stagedMap), + codeTutorialNumber: 4, + }) + .then(() => { + Toast.success('Code Tutorial submitted succesfully'); + }) + .catch(error => { + if (error instanceof ApiError) { + Toast.error(error.message); + } + }); + } break; case 'commit': if (!commitName) { @@ -180,7 +203,7 @@ export default function MapDesigner(props: MapDesignerProps): JSX.Element { useEffect(() => { MapDesignerUtils.setLocalStorageKey( - props.pageType == 'MapDesigner' + props.pageType == 'MapDesigner' || props.pageType == 'Tutorials' ? 'cc-map-designer-map' : 'dc-map-designer-map', ); diff --git a/src/components/NavBar/NavBar.module.css b/src/components/NavBar/NavBar.module.css index 5bc182b..3560abb 100644 --- a/src/components/NavBar/NavBar.module.css +++ b/src/components/NavBar/NavBar.module.css @@ -5,6 +5,8 @@ background-color: #232627; width: 100%; z-index: 1000; + padding-left: 1%; + padding-right: 1%; } .navBarContainer { @@ -60,6 +62,9 @@ .branding { position: relative; + display: flex; + flex-direction: row; + justify-content: space-between; } .profileIcons { @@ -119,6 +124,54 @@ opacity: 0.8; } +.tutorialIcon { + height: 37px; + width: auto; + margin-top: 5px; + position: relative; + margin-right: 2rem; +} + +.tutorialIcon:hover{ + cursor: pointer; + opacity: 0.8; +} + +.navbarTextButton { + background-color: #374043; + color: #fff; + border: none; + height: 50%; + width: max-content; + padding: 8px 10px; + font-size: 20px; + cursor: pointer; + border-radius: 5px; + margin-right: 40px; +} + +.navbarTextButton:hover { + background-color: #141515; +} +.codeContainer { + display: flex; + justify-content: center; + align-items: center; + height: 60px; +} +.codeTutorialHeading { + font-family: 'Bai Jamjuree', sans-serif; + color: #ffffff; + font-size: 28px; + margin: 0.5rem 1rem; +} +.notifIconContainer { + display: flex; + justify-content: center; + align-items: center; +} + + @media screen and (max-width:650px) { .navLogo { font-size: 20px; diff --git a/src/components/NavBar/NavBar.tsx b/src/components/NavBar/NavBar.tsx index 3a98a80..2efbf0c 100644 --- a/src/components/NavBar/NavBar.tsx +++ b/src/components/NavBar/NavBar.tsx @@ -17,18 +17,20 @@ import { apiConfig, ApiError } from '../../api/ApiConfig'; import Toast from 'react-hot-toast'; import DashboardOptions from '../DashboardOptions/DashboardOptions'; import { cookieDomain, dcEnable } from '../../config/config'; - import signUpIcon from '../../assets/sign_up.svg'; import signInIcon from '../../assets/sign_in.svg'; import challengeDone from '../../assets/challenge_done.png'; import challengeAvailable from '../../assets/challenge_available.png'; +import tutorialIcon from '../../assets/tutorial.png'; import DcCompleted from '../DcModals/DcCompleted'; import DcAvailable from '../DcModals/DcAvailable'; import { changePageState, changeSimulationState, dailyChallengeCompletionState, + dailyChallengePageState, } from '../../store/DailyChallenge/dailyChallenge'; +import ViewTutorial from '../TutorialModals/ViewTutorial'; const NavBar: React.FunctionComponent = () => { const dispatch = useAppDispatch(); @@ -37,6 +39,7 @@ const NavBar: React.FunctionComponent = () => { const loggedInUser = useAppSelector(user); const isLogged = useAppSelector(isloggedIn); const loadingAuth = useAppSelector(loading); + const pageState = useAppSelector(dailyChallengePageState); const dcCompletionstatus = useAppSelector(dailyChallengeCompletionState); useEffect(() => { const cookieValue = document.cookie; @@ -108,6 +111,18 @@ const NavBar: React.FunctionComponent = () => { setShowCompleted(false); }; + const [showTutorial, setShowTutorial] = useState(false); + const handleTutorialTake = () => { + dispatch(changePageState('Tutorials')); + navigate('/dashboard', { replace: true }); + setShowTutorial(false); + }; + const handleTutorialClose = () => { + dispatch(changePageState('Dashboard')); + navigate('/dashboard'); + setShowTutorial(false); + }; + return (
{ handleClose={handleCloseAvailable} handleTake={handleTake} /> +
{''}
+
+ {pageState == 'Tutorials' ? ( +
+ {'Code Tutorials'} +
+ ) : ( + <> + )} +
{(location.pathname === '/' || location.pathname === '/register') && @@ -159,6 +188,19 @@ const NavBar: React.FunctionComponent = () => { location.pathname != '/' ? (
+ {pageState == 'Dashboard' ? ( + { + setShowTutorial(true); + }} + /> + ) : ( + <> + )} + {dcEnable ? ( { updateTutorialLevel: 'SKIP', }) .then(() => { - Toast.success('Tutorial skipped'); + Toast.success('Tour skipped'); }) .catch(err => { if (err instanceof ApiError) Toast.error(err.message); diff --git a/src/components/TutorialModals/TutorialModals.module.css b/src/components/TutorialModals/TutorialModals.module.css new file mode 100644 index 0000000..da07fc0 --- /dev/null +++ b/src/components/TutorialModals/TutorialModals.module.css @@ -0,0 +1,52 @@ +.tutorialModal{ + background-color: #1e1e1e; + box-shadow: 0px 3px 6px 4px rgb(28 32 33 / 20%); + color: #aaaaaa; + font-family: 'Poppins'; + font-weight: 400; + font-size: 18px; + } + +.tutorialModal > *{ + border: 0; +} + +.headerText{ + font-family: 'Poppins'; + font-size: 28px; + color:#aaaaaa; + font-weight: bold; + } + +.tutorialModalHeader{ + background-color: #232627; + backdrop-filter: blur(100px); + height: 9vh; + border: 0px; +} + +.tutorialModalBtn{ + width: auto; + display: block; + border: 2px solid #aaaaaa; + margin-top: 15px; + padding: 2px 12px; + font-size: 18px; + font-family: 'Poppins'; + font-weight: 400; + color: #aaaaaa; + text-align: center; + margin-left: auto; + margin-right: auto; + margin-bottom: 10px; + background-color: transparent; +} + +.footer{ + display: flex; + flex-direction: row; + justify-content: space-evenly; + gap: 7rem; +} + + diff --git a/src/components/TutorialModals/TutorialModalsTypes.ts b/src/components/TutorialModals/TutorialModalsTypes.ts new file mode 100644 index 0000000..729c218 --- /dev/null +++ b/src/components/TutorialModals/TutorialModalsTypes.ts @@ -0,0 +1,5 @@ +export type TutorialProps = { + show: boolean; + handleTutorialClose: () => void; + handleTutorialTake?: () => void; +}; diff --git a/src/components/TutorialModals/ViewTutorial.tsx b/src/components/TutorialModals/ViewTutorial.tsx new file mode 100644 index 0000000..5843810 --- /dev/null +++ b/src/components/TutorialModals/ViewTutorial.tsx @@ -0,0 +1,45 @@ +import { Modal, Button } from 'react-bootstrap'; +import styles from './TutorialModals.module.css'; +import { TutorialProps } from './TutorialModalsTypes'; + +const ViewTutorial = (props: TutorialProps) => { + return ( + + + Code Tutorials + + + + Commander, we are here to help you started with some code tutorials of + the game. + + + + + + + ); +}; + +export default ViewTutorial; diff --git a/src/components/Websocket/Websocket.tsx b/src/components/Websocket/Websocket.tsx index ffe3975..f55dd30 100644 --- a/src/components/Websocket/Websocket.tsx +++ b/src/components/Websocket/Websocket.tsx @@ -4,7 +4,9 @@ import { Game, GameStatus, Notification, + TutorialGame, } from '@codecharacter-2024/client'; +import { RendererUtils } from '@codecharacter-2024/renderer'; import { apiConfig } from '../../api/ApiConfig'; import { Stomp } from '@stomp/stompjs'; import { useEffect, useState } from 'react'; @@ -13,13 +15,17 @@ import { getLogAction } from '../../store/rendererLogs/logSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { BASE_PATH } from '../../config/config'; import { CurrentGameType } from '../../store/editor/code'; -import { changeSimulationState } from '../../store/DailyChallenge/dailyChallenge'; +import { + changeSimulationState, + dailyChallengePageState, +} from '../../store/DailyChallenge/dailyChallenge'; export const Websocket: React.FunctionComponent = () => { const currentUserapi = new CurrentUserApi(apiConfig); const dispatch = useAppDispatch(); const gameType = useAppSelector(CurrentGameType); const [user, setUser] = useState(); + const pageType = useAppSelector(dailyChallengePageState); useEffect(() => { if (!user) return; @@ -29,34 +35,39 @@ export const Websocket: React.FunctionComponent = () => { wsClient.brokerURL = url; const handleConnect = () => { wsClient.subscribe(`/updates/${user.id}`, message => { - const game = JSON.parse(message.body) as Game; - switch (game.status) { - case GameStatus.Executing: - Toast.success('Executing now...'); - break; - case GameStatus.Executed: - Toast.success('Executed successfully!'); - // TODO: find non-hacky way to do this - dispatch(changeSimulationState(true)); - dispatch( - getLogAction({ - id: game.id, - callback: () => (window.location.href = './#/dashboard'), - gameType: gameType, - }), - ); - break; - case GameStatus.ExecuteError: - Toast.error('Execution error!'); - dispatch(changeSimulationState(true)); - dispatch( - getLogAction({ - id: game.id, - callback: () => (window.location.href = './#/dashboard'), - gameType: gameType, - }), - ); - break; + if (pageType === 'Tutorials') { + const game = JSON.parse(message.body) as TutorialGame; + RendererUtils.loadLog(game.logs ?? ''); + } else { + const game = JSON.parse(message.body) as Game; + switch (game.status) { + case GameStatus.Executing: + Toast.success('Executing now...'); + break; + case GameStatus.Executed: + Toast.success('Executed successfully!'); + // TODO: find non-hacky way to do this + dispatch(changeSimulationState(true)); + dispatch( + getLogAction({ + id: game.id, + callback: () => (window.location.href = './#/dashboard'), + gameType: gameType, + }), + ); + break; + case GameStatus.ExecuteError: + Toast.error('Execution error!'); + dispatch(changeSimulationState(true)); + dispatch( + getLogAction({ + id: game.id, + callback: () => (window.location.href = './#/dashboard'), + gameType: gameType, + }), + ); + break; + } } message.ack(); }); @@ -78,7 +89,7 @@ export const Websocket: React.FunctionComponent = () => { return () => { wsClient.deactivate(); }; - }, [user, gameType]); + }, [user, gameType, pageType]); useEffect(() => { if (localStorage.getItem('token') === null) return; diff --git a/src/pages/Dashboard/Dashboard.module.css b/src/pages/Dashboard/Dashboard.module.css index 6c16ef5..88873aa 100644 --- a/src/pages/Dashboard/Dashboard.module.css +++ b/src/pages/Dashboard/Dashboard.module.css @@ -49,6 +49,7 @@ .toolbarColumn { padding: 0px; + margin-right: 17px; } .toolbarColumn1 { @@ -109,6 +110,20 @@ outline: none; box-shadow: none; } +.toolbarTextButton { + background-color: #292929; + color: #fff; + border: none; + padding: 8px 10px; + font-size: 14px; + cursor: pointer; + border-radius: 5px; + margin-right: 10px; +} + +.toolbarTextButton:hover { + background-color: #141515; +} .toolbarButton2 { font-size: 0.9rem; @@ -255,3 +270,16 @@ color: #ffffff; transform: scale(1.15); } + +.codeContainer { + display: flex; + justify-content: center; + align-items: center; + height: 60px; + } +.codeTutorialHeading { +font-family: 'Bai Jamjuree', sans-serif; +color: #ffffff; +font-size: 17px; +margin: 0.5rem 1rem; +} diff --git a/src/pages/Dashboard/Dashboard.tsx b/src/pages/Dashboard/Dashboard.tsx index 395e91f..21a849f 100644 --- a/src/pages/Dashboard/Dashboard.tsx +++ b/src/pages/Dashboard/Dashboard.tsx @@ -4,6 +4,7 @@ import { DailyChallengesApi, CurrentUserApi, CodeType, + TutorialsApi, } from '@codecharacter-2024/client'; import { RendererComponent } from '@codecharacter-2024/renderer'; import Toast from 'react-hot-toast'; @@ -53,7 +54,7 @@ import { code2CommitNameChanged, isPvPSelfMatchModalOpened, } from '../../store/PvPSelfMatchMakeModal/PvPSelfMatchModal'; -import { loggedIn, user } from '../../store/User/UserSlice'; +import { codeTutorialLevel, loggedIn, user } from '../../store/User/UserSlice'; import { IsSettingsOpen, @@ -69,11 +70,18 @@ import { dailyChallengeState, initializeDailyChallengeState, dailyChallengePageState, - changeDcLanguage, dcCodeLanguage, dcCode, dcSimulation, + changeDcLanguage, } from '../../store/DailyChallenge/dailyChallenge'; +import { + initializeTutorialState, + changeTutorialLanguage, + tutorialCode, + tutorialCodeLanguage, + tutorialAllLanguagesCode, +} from '../../store/Tutorials/tutorials'; import Tour from '../../components/TourProvider/TourProvider'; import { EditorSteps } from '../../components/TourProvider/EditorSteps'; import { useNavigate } from 'react-router-dom'; @@ -134,13 +142,19 @@ export default function Dashboard(): JSX.Element { const pageState = useAppSelector(dailyChallengePageState); const dailyChallengeSimulationState = useAppSelector(dcSimulation); const currentGameType = useAppSelector(CurrentGameType); + const tutorialsCode = useAppSelector(tutorialCode); const userLanguage = pageState == 'Dashboard' ? useAppSelector(UserLanguage) - : useAppSelector(dcCodeLanguage); - + : pageState == 'DailyChallenge' + ? useAppSelector(dcCodeLanguage) + : useAppSelector(tutorialCodeLanguage); const codeAPI = new CodeApi(apiConfig); const dailyChallengeAPI = new DailyChallengesApi(apiConfig); + const tutorialAPI = new TutorialsApi(apiConfig); + const maxUserCodeTutorialLevel = useAppSelector(codeTutorialLevel); + const [codeTutorialNumber, setTutorialNumber] = useState(1); + const tutorialLanguagesCode = useAppSelector(tutorialAllLanguagesCode); useEffect(() => { const cookieValue = document.cookie; const bearerToken = cookieValue.split(';'); @@ -164,6 +178,17 @@ export default function Dashboard(): JSX.Element { }); } }, []); + useEffect(() => { + tutorialAPI + .getCodeTutorialByNumber(maxUserCodeTutorialLevel) + .then(response => { + setTutorialNumber(response.tutorialId ?? 1); + dispatch(initializeTutorialState(response)); + }) + .catch(err => { + if (err instanceof ApiError) Toast.error(err.message); + }); + }, []); useEffect(() => { if (localStorage.getItem('firstTime') === null) { @@ -206,6 +231,9 @@ export default function Dashboard(): JSX.Element { case 'DailyChallenge': dispatch(changeDcLanguage('c_cpp')); break; + case 'Tutorials': + dispatch(changeTutorialLanguage('c_cpp')); + break; default: dispatch(changeLanguage('c_cpp')); } @@ -213,17 +241,36 @@ export default function Dashboard(): JSX.Element { localStorage.setItem('languageChose', 'C++'); break; case 'Python': - pageState == 'Dashboard' - ? dispatch(changeLanguage('python')) - : dispatch(changeDcLanguage('python')); + switch (pageState) { + case 'Dashboard': + dispatch(changeLanguage('python')); + break; + case 'DailyChallenge': + dispatch(changeDcLanguage('python')); + break; + case 'Tutorials': + dispatch(changeTutorialLanguage('python')); + break; + default: + dispatch(changeLanguage('python')); + } setLanguageChose('Python'); localStorage.setItem('languageChose', 'Python'); break; case 'Java': - pageState == 'Dashboard' - ? dispatch(changeLanguage('java')) - : dispatch(changeDcLanguage('java')); - + switch (pageState) { + case 'Dashboard': + dispatch(changeLanguage('java')); + break; + case 'DailyChallenge': + dispatch(changeDcLanguage('java')); + break; + case 'Tutorials': + dispatch(changeTutorialLanguage('java')); + break; + default: + dispatch(changeLanguage('java')); + } setLanguageChose('Java'); localStorage.setItem('languageChose', 'Java'); break; @@ -252,7 +299,7 @@ export default function Dashboard(): JSX.Element { if (userLanguage === 'c_cpp') languageType = Language.Cpp; else if (userLanguage === 'python') languageType = Language.Python; else if (userLanguage === 'java') languageType = Language.Java; - + if (pageState == 'Tutorials') return; codeAPI .updateLatestCode({ codeType: @@ -309,33 +356,33 @@ export default function Dashboard(): JSX.Element { if (isInfoOpen === true) dispatch(isInfoOpened(false)); else dispatch(isInfoOpened(true)); } - const handleSubmit = () => { let languageType: Language = Language.Cpp; if (userLanguage === 'c_cpp') languageType = Language.Cpp; else if (userLanguage === 'python') languageType = Language.Python; else if (userLanguage === 'java') languageType = Language.Java; - - codeAPI - .updateLatestCode({ - codeType: - pageState == 'Dashboard' - ? currentGameType == GameType.NORMAL - ? 'NORMAL' - : 'PVP' - : 'DAILY_CHALLENGE', - code: pageState == 'Dashboard' ? userCode : dailyChallengeCode, - lock: true, - language: languageType, - }) - .then(() => { - if (pageState == 'Dashboard') { - Toast.success('Code Submitted'); - } - }) - .catch(err => { - if (err instanceof ApiError) Toast.error(err.message); - }); + if (pageState != 'Tutorials') { + codeAPI + .updateLatestCode({ + codeType: + pageState == 'Dashboard' + ? currentGameType == GameType.NORMAL + ? 'NORMAL' + : 'PVP' + : 'DAILY_CHALLENGE', + code: pageState == 'Dashboard' ? userCode : dailyChallengeCode, + lock: true, + language: languageType, + }) + .then(() => { + if (pageState == 'Dashboard') { + Toast.success('Code Submitted'); + } + }) + .catch(err => { + if (err instanceof ApiError) Toast.error(err.message); + }); + } if (pageState == 'DailyChallenge') { dailyChallengeAPI @@ -350,13 +397,53 @@ export default function Dashboard(): JSX.Element { if (err instanceof ApiError) Toast.error(err.message); }); } + if (pageState == 'Tutorials') { + tutorialAPI + .createCodeTutorialMatch({ + value: tutorialsCode, + language: languageType, + codeTutorialNumber: codeTutorialNumber, + }) + .then(() => { + Toast.success('Code Tutorial Submitted'); + }) + .catch(err => { + if (err instanceof ApiError) Toast.error(err.message); + }); + } + }; + const handleNextTutorial = () => { + tutorialAPI + .getCodeTutorialByNumber(codeTutorialNumber + 1) + .then(response => { + dispatch(initializeTutorialState(response)); + setTutorialNumber(codeTutorialNumber + 1); + }) + .catch(err => { + // if (err.message == 'Complete the current tutorial first') { + // setTutorialNumber(tutorialNumber - 1); + // } + if (err instanceof ApiError) Toast.error(err.message); + }); + }; + const handlePrevTutorial = () => { + tutorialAPI + .getCodeTutorialByNumber(codeTutorialNumber - 1) + .then(response => { + dispatch(initializeTutorialState(response)); + setTutorialNumber(codeTutorialNumber - 1); + }) + .catch(err => { + if (err.message == 'Complete the current tutorial first') { + setTutorialNumber(codeTutorialNumber - 1); + } + if (err instanceof ApiError) Toast.error(err.message); + }); }; - const currentUserApi = new CurrentUserApi(apiConfig); const User = useAppSelector(user); const navigate = useNavigate(); - const setOpened = (opened: boolean) => { if (opened === false) { currentUserApi @@ -565,6 +652,142 @@ export default function Dashboard(): JSX.Element {
+ ) : pageState == 'Tutorials' ? ( + +
+ + handleLanguageChange(e.target.value)} + id="LanguageSelector" + > + {languages.map(language => ( + + ))} + + +
+ {codeTutorialNumber == 4 ? ( + <> + ) : ( + + + + )} + <> + {codeTutorialNumber == 1 ? ( + <> + ) : ( + + )} + {codeTutorialNumber == 4 ? ( + <> + ) : ( + + )} + +
+ {pageState == 'Tutorials' ? ( +
+ {'Tutorial Number '} + {codeTutorialNumber} +
+ ) : ( + <> + )} +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
) : ( <>
@@ -596,13 +819,32 @@ export default function Dashboard(): JSX.Element { )}
{pageState == 'Dashboard' || - dailyChallenge.challType == 'MAP' ? ( + dailyChallenge.challType == 'MAP' || + (pageState == 'Tutorials' && codeTutorialNumber != 4) ? ( + ) : pageState == 'Tutorials' && codeTutorialNumber == 4 ? ( + ) : (
- {pageState == 'Dashboard' || dailyChallengeSimulationState ? ( + {pageState == 'Dashboard' || + (pageState == 'Tutorials' && codeTutorialNumber != 4) || + dailyChallengeSimulationState ? ( ) : dailyChallenge.challType == 'MAP' ? ( <> @@ -662,12 +910,16 @@ export default function Dashboard(): JSX.Element { >
+ ) : pageState == 'Tutorials' && codeTutorialNumber == 4 ? ( + ) : ( )}
- {pageState == 'Dashboard' || dailyChallengeSimulationState ? ( + {pageState == 'Dashboard' || + pageState == 'Tutorials' || + dailyChallengeSimulationState ? ( ) : ( <> diff --git a/src/pages/Statistics/Statistics.tsx b/src/pages/Statistics/Statistics.tsx index 39c331d..e5bf479 100644 --- a/src/pages/Statistics/Statistics.tsx +++ b/src/pages/Statistics/Statistics.tsx @@ -46,7 +46,6 @@ const Statistics = () => { } cdata.push(currentDataObject); } - console.log(cdata); setData(cdata); }) .catch(error => { diff --git a/src/store/DailyChallenge/dailyChallenge.ts b/src/store/DailyChallenge/dailyChallenge.ts index c44c8ba..49bf23f 100644 --- a/src/store/DailyChallenge/dailyChallenge.ts +++ b/src/store/DailyChallenge/dailyChallenge.ts @@ -14,7 +14,7 @@ import defaultJavaCode from '../../assets/codes/java/Run.java?raw'; export interface DailyChallengeStateType { dailyChallenge: DailyChallengeGetRequest; - pageType: 'Dashboard' | 'DailyChallenge'; + pageType: 'Dashboard' | 'DailyChallenge' | 'Tutorials'; dcCode: string; dcAllLanguagesCode: string[]; codeLanguage: string; @@ -67,7 +67,7 @@ export const dailyChallengeSlice = createSlice({ }, changePageState: ( state, - action: PayloadAction<'Dashboard' | 'DailyChallenge'>, + action: PayloadAction<'Dashboard' | 'DailyChallenge' | 'Tutorials'>, ) => { state.pageType = action.payload; }, @@ -116,7 +116,8 @@ export const dailyChallengeState = ( ): DailyChallengeGetRequest => state.dailyChallenge.dailyChallenge; export const dailyChallengePageState = ( state: RootState, -): 'Dashboard' | 'DailyChallenge' => state.dailyChallenge.pageType; +): 'Dashboard' | 'DailyChallenge' | 'Tutorials' => + state.dailyChallenge.pageType; export const dailyChallengeCompletionState = ( state: RootState, ): boolean | undefined => state.dailyChallenge.dailyChallenge.completionStatus; diff --git a/src/store/Tutorials/tutorials.ts b/src/store/Tutorials/tutorials.ts new file mode 100644 index 0000000..af24298 --- /dev/null +++ b/src/store/Tutorials/tutorials.ts @@ -0,0 +1,120 @@ +import { ChallengeType, TutorialsGetRequest } from '@codecharacter-2024/client'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { RootState } from '../store'; +import { CodeAndLanguage, languagesAvailable } from '../editor/code'; +import defaultCppCode from '../../assets/codes/cpp/run.cpp?raw'; +import defaultPythonCode from '../../assets/codes/python/run.py?raw'; +import defaultJavaCode from '../../assets/codes/java/Run.java?raw'; + +export interface TutorialStateType { + tutorials: TutorialsGetRequest; + tutorialCode: string | undefined; + tutorialAllLanguagesCode: string[]; + tutorialLanguage: string; + tutorialMap: Array>; + isCompleted: boolean; + tutorialId: number; +} + +const initialState: TutorialStateType = { + tutorials: { + tutorialName: '', + description: '', + tutorialType: '' as ChallengeType, + tutorialCodes: { + cpp: ' ', + java: ' ', + python: ' ', + image: ' ', + }, + }, + tutorialCode: defaultCppCode, + tutorialAllLanguagesCode: [ + defaultCppCode, + defaultPythonCode, + defaultJavaCode, + ], + tutorialLanguage: 'c_cpp', + tutorialMap: [], + isCompleted: false, + tutorialId: 1, +}; + +export const tutorialsSlice = createSlice({ + name: 'tutorialsState', + initialState, + reducers: { + initializeTutorialState: ( + state, + action: PayloadAction, + ) => { + state.tutorials.tutorialName = action.payload.tutorialName; + (state.tutorials.description = action.payload.description + ? action.payload.description + : ''), + (state.tutorials.tutorialCodes = action.payload.tutorialCodes); + state.tutorialCode = action.payload.tutorialCodes.cpp; + state.tutorialAllLanguagesCode[0] = action.payload.tutorialCodes.cpp; + state.tutorialAllLanguagesCode[1] = action.payload.tutorialCodes.python; + state.tutorialAllLanguagesCode[2] = action.payload.tutorialCodes.java; + state.isCompleted = false; + }, + changeTutorialCode: (state, action: PayloadAction) => { + const tempCurrentUserLanguage = action.payload.currentUserLanguage; + const desiredIndex = languagesAvailable.indexOf(tempCurrentUserLanguage); + const newCodeAndLanguage: CodeAndLanguage = { + currentUserCode: action.payload.currentUserCode, + currentUserLanguage: action.payload.currentUserLanguage, + }; + state.tutorialAllLanguagesCode[desiredIndex] = + newCodeAndLanguage.currentUserCode; + state.tutorialCode = newCodeAndLanguage.currentUserCode; + }, + changeTutorialLanguage: (state, action: PayloadAction) => { + const tempCurrentUserLanguage = action.payload; + const desiredIndex = languagesAvailable.indexOf(tempCurrentUserLanguage); + state.tutorialCode = state.tutorialAllLanguagesCode[desiredIndex]; + state.tutorialLanguage = action.payload; + }, + changeTutorialMap: (state, action: PayloadAction>>) => { + state.tutorialMap = action.payload; + }, + changeTutorialId: (state, action: PayloadAction) => { + if (action.payload == 1) { + state.tutorialId = state.tutorialId + 1; + } else { + state.tutorialId = state.tutorialId - 1; + } + }, + changeCompletionState: (state, action: PayloadAction) => { + state.isCompleted = action.payload; + }, + }, +}); + +export const { + initializeTutorialState, + changeTutorialCode, + changeTutorialLanguage, + changeTutorialMap, + changeCompletionState, + changeTutorialId, +} = tutorialsSlice.actions; +export const tutorialState = (state: RootState): TutorialsGetRequest => + state.tutorials.tutorials; +export const tutorialCode = (state: RootState): string => + state.tutorials.tutorialCode; +export const tutorialDescription = (state: RootState): string | undefined => + state.tutorials.tutorials.description; +export const tutorialId = (state: RootState): number => + state.tutorials.tutorialId; +export const tutorialCodeLanguage = (state: RootState): string => + state.tutorials.tutorialLanguage; +export const tutorialMap = (state: RootState): Array> => + state.tutorials.tutorialMap; +export const tutorialCompletion = (state: RootState): boolean => + state.tutorials.isCompleted; +export const tutorialAllLanguagesCode = (state: RootState): string[] => + state.tutorials.tutorialAllLanguagesCode; + +export default tutorialsSlice.reducer; diff --git a/src/store/User/UserApi.ts b/src/store/User/UserApi.ts index 5ff471e..8384eae 100644 --- a/src/store/User/UserApi.ts +++ b/src/store/User/UserApi.ts @@ -66,6 +66,7 @@ export const getUserDetails = (): Promise<{ country: string; avatarId: number; isTutorialComplete: boolean; + codeTutorialNumber: number | undefined; }> => { return new Promise((resolve, reject) => { const currentUserapi = new CurrentUserApi(apiConfig); @@ -82,6 +83,7 @@ export const getUserDetails = (): Promise<{ country: res.country, avatarId: res.avatarId, isTutorialComplete: res.isTutorialComplete, + codeTutorialNumber: res.codeTutorialLevel, }); }) .catch(error => { diff --git a/src/store/User/UserSlice.ts b/src/store/User/UserSlice.ts index fec847a..258840d 100644 --- a/src/store/User/UserSlice.ts +++ b/src/store/User/UserSlice.ts @@ -20,6 +20,7 @@ export interface User { college: string; avatarId: number; recaptchaCode: string; + codeTutorialNumber: number; } interface register { @@ -48,6 +49,7 @@ const initialState: register = { college: '', avatarId: 0, recaptchaCode: '', + codeTutorialNumber: 1, }, loading: false, isRegistered: false, @@ -203,6 +205,7 @@ export const UserSlice = createSlice({ state.user.country = action.payload.country; state.user.college = action.payload.college; state.user.avatarId = action.payload.avatarId; + state.user.codeTutorialNumber = action.payload.codeTutorialNumber; }) .addCase(getUserDetailsAction.rejected, state => { state.loading = false; @@ -259,5 +262,7 @@ export const isSuccess = (state: RootState): boolean => state.user.isSuccessCreditonals; export const isSuccessUser = (state: RootState): boolean => state.user.isSuccessUser; +export const codeTutorialLevel = (state: RootState): number => + state.user.user.codeTutorialNumber; export default UserSlice.reducer; diff --git a/src/store/store.ts b/src/store/store.ts index 44a51f0..3aa66be 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -14,7 +14,7 @@ import logReducer from './rendererLogs/logSlice'; import selfMatchModalReducer from './SelfMatchMakeModal/SelfMatchModal'; import PvPSelfMatchModalReducer from './PvPSelfMatchMakeModal/PvPSelfMatchModal'; import dailyChallengeReducer from './DailyChallenge/dailyChallenge'; - +import tutorialReducer from './Tutorials/tutorials'; const reducers = combineReducers({ editorState: editorReducer, settingsState: settingsReducer, @@ -38,6 +38,7 @@ export const store = configureStore({ selfMatchModal: selfMatchModalReducer, pvpSelfMatchModal: PvPSelfMatchModalReducer, dailyChallenge: dailyChallengeReducer, + tutorials: tutorialReducer, }, middleware: [thunk], });