diff --git a/src/api/src/helpers/toPublicUser.ts b/src/api/src/helpers/toPublicUser.ts index 33109aa..6a142fd 100644 --- a/src/api/src/helpers/toPublicUser.ts +++ b/src/api/src/helpers/toPublicUser.ts @@ -6,6 +6,7 @@ export const toPublicUser = (user: User): PublicUser => { _id: user._id, username: user.username, emailVerified: user.emailVerified, + progress: user.progress, createdAt: user.createdAt, } } diff --git a/src/api/src/resolvers/user/getPublicUser/getPublicUser.spec.ts b/src/api/src/resolvers/page/getPublicUser/getPublicUser.spec.ts similarity index 100% rename from src/api/src/resolvers/user/getPublicUser/getPublicUser.spec.ts rename to src/api/src/resolvers/page/getPublicUser/getPublicUser.spec.ts diff --git a/src/api/src/resolvers/user/getPublicUser/getPublicUser.ts b/src/api/src/resolvers/page/getPublicUser/getPublicUser.ts similarity index 95% rename from src/api/src/resolvers/user/getPublicUser/getPublicUser.ts rename to src/api/src/resolvers/page/getPublicUser/getPublicUser.ts index cc17864..908ba32 100644 --- a/src/api/src/resolvers/user/getPublicUser/getPublicUser.ts +++ b/src/api/src/resolvers/page/getPublicUser/getPublicUser.ts @@ -4,11 +4,11 @@ import { Context, Next } from 'koa' import { firstError } from '../../../helpers/firstError' import { ResponseError } from '../../../shared/mongo/ResponseError' -import { GetPublicUserInputs, GetPublicUserOutputs } from '../../../shared/user/GetPublicUser' +import { GetPublicUserInputs, GetPublicUserOutputs } from '../../../shared/page/GetPublicUser' import { PublicUser } from '../../../shared/user/PublicUser' import { UserModel } from '../../../shared/user/User' -export const PUBLIC_USER_MONGO_SELECTOR = '_id username emailVerified createdAt' +export const PUBLIC_USER_MONGO_SELECTOR = '_id username emailVerified progress createdAt' export const getPublicUser = async (ctx: Context, next: Next): Promise => { const getPublicUserArgs = plainToClass(GetPublicUserInputs, ctx.request.body, { excludeExtraneousValues: true }) diff --git a/src/api/src/resolvers/user/addProgress/addProgress.spec.ts b/src/api/src/resolvers/user/addProgress/addProgress.spec.ts new file mode 100644 index 0000000..ab5cc24 --- /dev/null +++ b/src/api/src/resolvers/user/addProgress/addProgress.spec.ts @@ -0,0 +1,45 @@ +import { Context, Next } from 'koa' + +import { Jwt } from '../../../shared/user/Jwt' +import { User } from '../../../shared/user/User' +import { createTestUser } from '../../../test/createTestUser' +import { deleteTestUser } from '../../../test/deleteTestUser' +import { mockConnect } from '../../../test/mockConnect' +import { addProgress } from './addProgress' + +let user: User +let next: Next +let jwt: Jwt + +describe('User', () => { + beforeAll(async () => { + await mockConnect() + const created = await createTestUser('bob@test.com', 'bob', 'Bob1234#') + user = created.user + jwt = created.jwt + next = created.next + }) + + it('can add progress', async (done) => { + const ctx: Context = { + request: { + headers: { + authorization: 'Bearer ' + jwt, + }, + body: { + chapterDone: '/pascal/chapter-polymorphism', + }, + }, + } as Context + + await addProgress(ctx, next) + + expect(ctx.body.user).toBeDefined() + expect(ctx.body.user.progress).toContain('/pascal/chapter-polymorphism') + done() + }) + + afterAll(async () => { + await deleteTestUser(user._id) + }) +}) diff --git a/src/api/src/resolvers/user/addProgress/addProgress.ts b/src/api/src/resolvers/user/addProgress/addProgress.ts new file mode 100644 index 0000000..6bdf04d --- /dev/null +++ b/src/api/src/resolvers/user/addProgress/addProgress.ts @@ -0,0 +1,41 @@ +import { plainToClass } from 'class-transformer' +import { validateOrReject } from 'class-validator' +import { Context, Next } from 'koa' + +import { firstError } from '../../../helpers/firstError' +import { toPublicUser } from '../../../helpers/toPublicUser' +import { AddProgressInputs, AddProgressOutputs } from '../../../shared/user/AddProgress' +import { PublicUser } from '../../../shared/user/PublicUser' +import { User, UserModel } from '../../../shared/user/User' +import { rateLimit } from '../../quota/rateLimit/rateLimit' +import { authenticate } from '../helpers/authenticate' + +export const PUBLIC_USER_MONGO_SELECTOR = '_id username emailVerified createdAt' + +export const addProgress = async (ctx: Context, next: Next): Promise => { + const addProgressArgs = plainToClass(AddProgressInputs, ctx.request.body, { excludeExtraneousValues: true }) + await validateOrReject(addProgressArgs, { forbidUnknownValues: true }).catch(firstError) + const { chapterDone } = addProgressArgs + + const user: User = await authenticate(ctx) + + await rateLimit(user._id) + + await UserModel.updateOne( + { _id: user._id }, + { $addToSet: { progress: chapterDone } }, + ).exec() + + const updatedUser: User = await UserModel.findOne( + { _id: user._id }, + ).lean() as User + + const publicUser: PublicUser = toPublicUser(updatedUser) + + const response: AddProgressOutputs = { user: publicUser } + + ctx.status = 200 + ctx.body = response + + await next() +} diff --git a/src/api/src/router.ts b/src/api/src/router.ts index 27747c7..a3599df 100644 --- a/src/api/src/router.ts +++ b/src/api/src/router.ts @@ -1,14 +1,15 @@ import * as Router from '@koa/router' import { Context } from 'koa' -import { signUp } from './resolvers/user/signUp/signUp' +import { getPublicUser } from './resolvers/page/getPublicUser/getPublicUser' +import { addProgress } from './resolvers/user/addProgress/addProgress' +import { changePassword } from './resolvers/user/changePassword/changePassword' +import { forgotPassword } from './resolvers/user/forgotPassword/forgotPassword' import { login } from './resolvers/user/login/login' -import { verifyEmail } from './resolvers/user/verifyEmail/verifyEmail' -import { resetPassword } from './resolvers/user/resetPassword/resetPassword' import { resendEmailVerification } from './resolvers/user/resendEmailVerification/resendEmailVerification' -import { getPublicUser } from './resolvers/user/getPublicUser/getPublicUser' -import { forgotPassword } from './resolvers/user/forgotPassword/forgotPassword' -import { changePassword } from './resolvers/user/changePassword/changePassword' +import { resetPassword } from './resolvers/user/resetPassword/resetPassword' +import { signUp } from './resolvers/user/signUp/signUp' +import { verifyEmail } from './resolvers/user/verifyEmail/verifyEmail' const router = new Router() @@ -21,6 +22,7 @@ router.post('/user/login', login) router.post('/user/verify-email', verifyEmail) router.post('/user/resend-email-verification', resendEmailVerification) router.post('/user/get-public-user', getPublicUser) +router.post('/user/add-progress', addProgress) router.post('/user/reset-password', resetPassword) router.post('/user/forgot-password', forgotPassword) router.post('/user/change-password', changePassword) diff --git a/src/api/src/shared/user/GetPublicUser.ts b/src/api/src/shared/page/GetPublicUser.ts similarity index 87% rename from src/api/src/shared/user/GetPublicUser.ts rename to src/api/src/shared/page/GetPublicUser.ts index a5f8e7f..95ff90b 100644 --- a/src/api/src/shared/user/GetPublicUser.ts +++ b/src/api/src/shared/page/GetPublicUser.ts @@ -1,6 +1,6 @@ import { Length, Matches } from 'class-validator' import { Expose } from 'class-transformer' -import { PublicUser } from './PublicUser' +import { PublicUser } from '../user/PublicUser' export class GetPublicUserInputs { @Expose() diff --git a/src/api/src/shared/user/AddProgress.ts b/src/api/src/shared/user/AddProgress.ts new file mode 100644 index 0000000..02488fd --- /dev/null +++ b/src/api/src/shared/user/AddProgress.ts @@ -0,0 +1,15 @@ +import { Expose } from 'class-transformer' +import { Length, Matches } from 'class-validator' + +import { PublicUser } from '../user/PublicUser' + +export class AddProgressInputs { + @Expose() + @Length(2, 100) + @Matches(/^[a-zA-Z0-9-\/]*$/, { message: 'Chapter slug can only contain letters, numbers, dashes and slashes' }) + chapterDone!: string +} + +export class AddProgressOutputs { + user!: PublicUser +} diff --git a/src/api/src/shared/user/PublicUser.ts b/src/api/src/shared/user/PublicUser.ts index 4a29788..3387c27 100644 --- a/src/api/src/shared/user/PublicUser.ts +++ b/src/api/src/shared/user/PublicUser.ts @@ -1,4 +1,4 @@ -import { IsDate, IsEmail, IsMongoId, Length, Matches } from 'class-validator' +import { IsArray, IsDate, IsEmail, IsMongoId, Length, Matches } from 'class-validator' import { ObjectId } from 'mongodb' export class PublicUser { @@ -12,6 +12,9 @@ export class PublicUser { @IsEmail() emailVerified?: boolean + @IsArray() + progress?: string[] + @IsDate() createdAt!: Date } diff --git a/src/api/src/shared/user/User.ts b/src/api/src/shared/user/User.ts index 0243426..ebe7332 100644 --- a/src/api/src/shared/user/User.ts +++ b/src/api/src/shared/user/User.ts @@ -25,6 +25,9 @@ export class User { @Property({ required: true }) hashedPassword!: string + @Property({ nullable: true, optional: true }) + progress?: string[] + @IsDate() createdAt!: Date diff --git a/src/frontend/src/app/App.components/Header/Header.view.tsx b/src/frontend/src/app/App.components/Header/Header.view.tsx index 2371bb8..f134603 100644 --- a/src/frontend/src/app/App.components/Header/Header.view.tsx +++ b/src/frontend/src/app/App.components/Header/Header.view.tsx @@ -5,7 +5,7 @@ import { JwtDecoded } from 'shared/user/JwtDecoded' import { Hamburger } from '../Hamburger/Hamburger.controller' // prettier-ignore -import { HeaderBg, HeaderLogo, HeaderStyled, HeaderLoggedOut, HeaderLoggedIn, HeaderMenuItem } from "./Header.style"; +import { HeaderBg, HeaderLoggedIn, HeaderLoggedOut, HeaderLogo, HeaderMenuItem, HeaderStyled } from "./Header.style"; type HeaderViewProps = { user?: JwtDecoded | undefined @@ -49,7 +49,14 @@ function loggedOutHeader() { function loggedInHeader({ user, removeAuthUserCallback }: HeaderViewProps) { return ( - {user?.username} + { + removeAuthUserCallback() + }} + > + {user?.username} + { diff --git a/src/frontend/src/app/App.routes.tsx b/src/frontend/src/app/App.routes.tsx index bc578a1..212c01e 100644 --- a/src/frontend/src/app/App.routes.tsx +++ b/src/frontend/src/app/App.routes.tsx @@ -9,6 +9,7 @@ import { Home } from 'pages/Home/Home.controller' import { Login } from 'pages/Login/Login.controller' import { ResetPassword } from 'pages/ResetPassword/ResetPassword.controller' import { SignUp } from 'pages/SignUp/SignUp.controller' +import { User } from 'pages/User/User.controller' import { VerifyEmail } from 'pages/VerifyEmail/VerifyEmail.controller' import React from 'react' import { Route, Switch } from 'react-router-dom' @@ -54,6 +55,9 @@ export const AppRoutes = ({ location }: any) => ( + + + diff --git a/src/frontend/src/pages/Blank/Blank.actions.tsx b/src/frontend/src/pages/Blank/Blank.actions.tsx index 71f1ba6..23c4c30 100644 --- a/src/frontend/src/pages/Blank/Blank.actions.tsx +++ b/src/frontend/src/pages/Blank/Blank.actions.tsx @@ -1,5 +1,5 @@ import { store } from 'index' -import { GetPublicUserInputs } from 'shared/user/GetPublicUser' +import { GetPublicUserInputs } from 'shared/page/GetPublicUser' export const GET_BLANK_REQUEST = 'GET_BLANK_REQUEST' export const GET_BLANK_COMMIT = 'GET_BLANK_COMMIT' diff --git a/src/frontend/src/pages/Chapter/Chapter.data.tsx b/src/frontend/src/pages/Chapter/Chapter.data.tsx index d751a28..194c32c 100644 --- a/src/frontend/src/pages/Chapter/Chapter.data.tsx +++ b/src/frontend/src/pages/Chapter/Chapter.data.tsx @@ -1,402 +1,397 @@ -/* prettier-ignore */ - -import { data as pascalDataAddresses } from "../Chapters/Pascal/ChapterAddresses"; -import { data as pascalDataBuiltIns } from "../Chapters/Pascal/ChapterBuiltIns"; -import { data as pascalDataConditionals } from "../Chapters/Pascal/ChapterConditionals"; -import { data as pascalDataFunctions } from "../Chapters/Pascal/ChapterFunctions"; -import { data as pascalDataInteractions } from "../Chapters/Pascal/ChapterInteractions"; -import { data as pascalDataLists } from "../Chapters/Pascal/ChapterLists"; -import { data as pascalDataLoops } from "../Chapters/Pascal/ChapterLoops"; -import { data as pascalDataMainFunction } from "../Chapters/Pascal/ChapterMainFunction"; -import { data as pascalDataMaps } from "../Chapters/Pascal/ChapterMaps"; -import { data as pascalDataMath } from "../Chapters/Pascal/ChapterMath"; -import { data as pascalDataOption } from "../Chapters/Pascal/ChapterOption"; -import { data as pascalDataRecords } from "../Chapters/Pascal/ChapterRecords"; -import { data as pascalDataStrings } from "../Chapters/Pascal/ChapterStrings"; -import { data as pascalDataTimestamps } from "../Chapters/Pascal/ChapterTimestamps"; -import { data as pascalDataTransactions } from "../Chapters/Pascal/ChapterTransactions"; -import { data as pascalDataTuples } from "../Chapters/Pascal/ChapterTuples"; -import { data as pascalDataTypes } from "../Chapters/Pascal/ChapterTypes"; -import { data as pascalDataVariables } from "../Chapters/Pascal/ChapterVariables"; -import { data as pascalDataVariant } from "../Chapters/Pascal/ChapterVariant"; -import { data as pascalDataPolymorphism } from "../Chapters/Pascal/ChapterPolymorphism"; -import { data as pascalDataDeployContract } from "../Chapters/Pascal/ChapterDeployContract"; -import { data as pascalDataFA12 } from "../Chapters/Pascal/ChapterFA12"; -import { data as pascalDataLambda } from "../Chapters/Pascal/ChapterLambda"; -import { data as pascalDataMultisig } from "../Chapters/Pascal/ChapterMultisig"; - - -import { data as camelDataAddresses } from "../Chapters/Camel/ChapterAddresses"; -import { data as camelDataBuiltIns } from "../Chapters/Camel/ChapterBuiltIns"; -import { data as camelDataConditionals } from "../Chapters/Camel/ChapterConditionals"; -import { data as camelDataFunctions } from "../Chapters/Camel/ChapterFunctions"; -import { data as camelDataInteractions } from "../Chapters/Camel/ChapterInteractions"; -import { data as camelDataLists } from "../Chapters/Camel/ChapterLists"; -import { data as camelDataLoops } from "../Chapters/Camel/ChapterLoops"; -import { data as camelDataMainFunction } from "../Chapters/Camel/ChapterMainFunction"; -import { data as camelDataMaps } from "../Chapters/Camel/ChapterMaps"; -import { data as camelDataMath } from "../Chapters/Camel/ChapterMath"; -import { data as camelDataOption } from "../Chapters/Camel/ChapterOption"; -import { data as camelDataRecords } from "../Chapters/Camel/ChapterRecords"; -import { data as camelDataStrings } from "../Chapters/Camel/ChapterStrings"; -import { data as camelDataTimestamps } from "../Chapters/Camel/ChapterTimestamps"; -import { data as camelDataTransactions } from "../Chapters/Camel/ChapterTransactions"; -import { data as camelDataTuples } from "../Chapters/Camel/ChapterTuples"; -import { data as camelDataTypes } from "../Chapters/Camel/ChapterTypes"; -import { data as camelDataVariables } from "../Chapters/Camel/ChapterVariables"; -import { data as camelDataVariant } from "../Chapters/Camel/ChapterVariant"; -import { data as camelDataPolymorphism } from "../Chapters/Camel/ChapterPolymorphism"; -import { data as camelDataDeployContract } from "../Chapters/Camel/ChapterDeployContract"; -import { data as camelDataFA12 } from "../Chapters/Camel/ChapterFA12"; -import { data as camelDataLambda } from "../Chapters/Camel/ChapterLambda"; -import { data as camelDataMultisig } from "../Chapters/Camel/ChapterMultisig"; - -import { data as reasonDataAddresses } from "../Chapters/Reason/ChapterAddresses"; -import { data as reasonDataBuiltIns } from "../Chapters/Reason/ChapterBuiltIns"; -import { data as reasonDataConditionals } from "../Chapters/Reason/ChapterConditionals"; -import { data as reasonDataFunctions } from "../Chapters/Reason/ChapterFunctions"; -import { data as reasonDataInteractions } from "../Chapters/Reason/ChapterInteractions"; -import { data as reasonDataLists } from "../Chapters/Reason/ChapterLists"; -import { data as reasonDataLoops } from "../Chapters/Reason/ChapterLoops"; -import { data as reasonDataMainFunction } from "../Chapters/Reason/ChapterMainFunction"; -import { data as reasonDataMaps } from "../Chapters/Reason/ChapterMaps"; -import { data as reasonDataMath } from "../Chapters/Reason/ChapterMath"; -import { data as reasonDataOption } from "../Chapters/Reason/ChapterOption"; -import { data as reasonDataRecords } from "../Chapters/Reason/ChapterRecords"; -import { data as reasonDataStrings } from "../Chapters/Reason/ChapterStrings"; -import { data as reasonDataTimestamps } from "../Chapters/Reason/ChapterTimestamps"; -import { data as reasonDataTransactions } from "../Chapters/Reason/ChapterTransactions"; -import { data as reasonDataTuples } from "../Chapters/Reason/ChapterTuples"; -import { data as reasonDataTypes } from "../Chapters/Reason/ChapterTypes"; -import { data as reasonDataVariables } from "../Chapters/Reason/ChapterVariables"; -import { data as reasonDataVariant } from "../Chapters/Reason/ChapterVariant"; -import { data as reasonDataPolymorphism } from "../Chapters/Reason/ChapterPolymorphism"; -import { data as reasonDataDeployContract } from "../Chapters/Reason/ChapterDeployContract"; -import { data as reasonDataFA12 } from "../Chapters/Reason/ChapterFA12"; -import { data as reasonDataLambda } from "../Chapters/Reason/ChapterLambda"; -import { data as reasonDataMultisig } from "../Chapters/Reason/ChapterMultisig"; +import { data as camelDataAddresses } from '../Chapters/Camel/ChapterAddresses' +import { data as camelDataBuiltIns } from '../Chapters/Camel/ChapterBuiltIns' +import { data as camelDataConditionals } from '../Chapters/Camel/ChapterConditionals' +import { data as camelDataDeployContract } from '../Chapters/Camel/ChapterDeployContract' +import { data as camelDataFA12 } from '../Chapters/Camel/ChapterFA12' +import { data as camelDataFunctions } from '../Chapters/Camel/ChapterFunctions' +import { data as camelDataInteractions } from '../Chapters/Camel/ChapterInteractions' +import { data as camelDataLambda } from '../Chapters/Camel/ChapterLambda' +import { data as camelDataLists } from '../Chapters/Camel/ChapterLists' +import { data as camelDataLoops } from '../Chapters/Camel/ChapterLoops' +import { data as camelDataMainFunction } from '../Chapters/Camel/ChapterMainFunction' +import { data as camelDataMaps } from '../Chapters/Camel/ChapterMaps' +import { data as camelDataMath } from '../Chapters/Camel/ChapterMath' +import { data as camelDataMultisig } from '../Chapters/Camel/ChapterMultisig' +import { data as camelDataOption } from '../Chapters/Camel/ChapterOption' +import { data as camelDataPolymorphism } from '../Chapters/Camel/ChapterPolymorphism' +import { data as camelDataRecords } from '../Chapters/Camel/ChapterRecords' +import { data as camelDataStrings } from '../Chapters/Camel/ChapterStrings' +import { data as camelDataTimestamps } from '../Chapters/Camel/ChapterTimestamps' +import { data as camelDataTransactions } from '../Chapters/Camel/ChapterTransactions' +import { data as camelDataTuples } from '../Chapters/Camel/ChapterTuples' +import { data as camelDataTypes } from '../Chapters/Camel/ChapterTypes' +import { data as camelDataVariables } from '../Chapters/Camel/ChapterVariables' +import { data as camelDataVariant } from '../Chapters/Camel/ChapterVariant' +import { data as pascalDataAddresses } from '../Chapters/Pascal/ChapterAddresses' +import { data as pascalDataBuiltIns } from '../Chapters/Pascal/ChapterBuiltIns' +import { data as pascalDataConditionals } from '../Chapters/Pascal/ChapterConditionals' +import { data as pascalDataDeployContract } from '../Chapters/Pascal/ChapterDeployContract' +import { data as pascalDataFA12 } from '../Chapters/Pascal/ChapterFA12' +import { data as pascalDataFunctions } from '../Chapters/Pascal/ChapterFunctions' +import { data as pascalDataInteractions } from '../Chapters/Pascal/ChapterInteractions' +import { data as pascalDataLambda } from '../Chapters/Pascal/ChapterLambda' +import { data as pascalDataLists } from '../Chapters/Pascal/ChapterLists' +import { data as pascalDataLoops } from '../Chapters/Pascal/ChapterLoops' +import { data as pascalDataMainFunction } from '../Chapters/Pascal/ChapterMainFunction' +import { data as pascalDataMaps } from '../Chapters/Pascal/ChapterMaps' +import { data as pascalDataMath } from '../Chapters/Pascal/ChapterMath' +import { data as pascalDataMultisig } from '../Chapters/Pascal/ChapterMultisig' +import { data as pascalDataOption } from '../Chapters/Pascal/ChapterOption' +import { data as pascalDataPolymorphism } from '../Chapters/Pascal/ChapterPolymorphism' +import { data as pascalDataRecords } from '../Chapters/Pascal/ChapterRecords' +import { data as pascalDataStrings } from '../Chapters/Pascal/ChapterStrings' +import { data as pascalDataTimestamps } from '../Chapters/Pascal/ChapterTimestamps' +import { data as pascalDataTransactions } from '../Chapters/Pascal/ChapterTransactions' +import { data as pascalDataTuples } from '../Chapters/Pascal/ChapterTuples' +import { data as pascalDataTypes } from '../Chapters/Pascal/ChapterTypes' +import { data as pascalDataVariables } from '../Chapters/Pascal/ChapterVariables' +import { data as pascalDataVariant } from '../Chapters/Pascal/ChapterVariant' +import { data as reasonDataAddresses } from '../Chapters/Reason/ChapterAddresses' +import { data as reasonDataBuiltIns } from '../Chapters/Reason/ChapterBuiltIns' +import { data as reasonDataConditionals } from '../Chapters/Reason/ChapterConditionals' +import { data as reasonDataDeployContract } from '../Chapters/Reason/ChapterDeployContract' +import { data as reasonDataFA12 } from '../Chapters/Reason/ChapterFA12' +import { data as reasonDataFunctions } from '../Chapters/Reason/ChapterFunctions' +import { data as reasonDataInteractions } from '../Chapters/Reason/ChapterInteractions' +import { data as reasonDataLambda } from '../Chapters/Reason/ChapterLambda' +import { data as reasonDataLists } from '../Chapters/Reason/ChapterLists' +import { data as reasonDataLoops } from '../Chapters/Reason/ChapterLoops' +import { data as reasonDataMainFunction } from '../Chapters/Reason/ChapterMainFunction' +import { data as reasonDataMaps } from '../Chapters/Reason/ChapterMaps' +import { data as reasonDataMath } from '../Chapters/Reason/ChapterMath' +import { data as reasonDataMultisig } from '../Chapters/Reason/ChapterMultisig' +import { data as reasonDataOption } from '../Chapters/Reason/ChapterOption' +import { data as reasonDataPolymorphism } from '../Chapters/Reason/ChapterPolymorphism' +import { data as reasonDataRecords } from '../Chapters/Reason/ChapterRecords' +import { data as reasonDataStrings } from '../Chapters/Reason/ChapterStrings' +import { data as reasonDataTimestamps } from '../Chapters/Reason/ChapterTimestamps' +import { data as reasonDataTransactions } from '../Chapters/Reason/ChapterTransactions' +import { data as reasonDataTuples } from '../Chapters/Reason/ChapterTuples' +import { data as reasonDataTypes } from '../Chapters/Reason/ChapterTypes' +import { data as reasonDataVariables } from '../Chapters/Reason/ChapterVariables' +import { data as reasonDataVariant } from '../Chapters/Reason/ChapterVariant' export const chapterData = [ { - pathname: "/pascal/chapter-about", - language: "PascaLIGO", - name: "1 - Pascal - About", + pathname: '/pascal/chapter-about', + language: 'PascaLIGO', + name: '1 - Pascal - About', data: { course: undefined, exercise: undefined, solution: undefined }, }, - { pathname: "/pascal/chapter-types", language: "PascaLIGO", name: "2 - Pascal - Types", data: pascalDataTypes }, + { pathname: '/pascal/chapter-types', language: 'PascaLIGO', name: '2 - Pascal - Types', data: pascalDataTypes }, { - pathname: "/pascal/chapter-variables", - language: "PascaLIGO", - name: "3 - Pascal - Variables", + pathname: '/pascal/chapter-variables', + language: 'PascaLIGO', + name: '3 - Pascal - Variables', data: pascalDataVariables, }, - { pathname: "/pascal/chapter-math", language: "PascaLIGO", name: "4 - Pascal - Math", data: pascalDataMath }, - { pathname: "/pascal/chapter-strings", language: "PascaLIGO", name: "5 - Pascal - Strings", data: pascalDataStrings }, + { pathname: '/pascal/chapter-math', language: 'PascaLIGO', name: '4 - Pascal - Math', data: pascalDataMath }, + { pathname: '/pascal/chapter-strings', language: 'PascaLIGO', name: '5 - Pascal - Strings', data: pascalDataStrings }, { - pathname: "/pascal/chapter-functions", - language: "PascaLIGO", - name: "6 - Pascal - Functions", + pathname: '/pascal/chapter-functions', + language: 'PascaLIGO', + name: '6 - Pascal - Functions', data: pascalDataFunctions, }, { - pathname: "/pascal/chapter-conditionals", - language: "PascaLIGO", - name: "7 - Pascal - Conditionals", + pathname: '/pascal/chapter-conditionals', + language: 'PascaLIGO', + name: '7 - Pascal - Conditionals', data: pascalDataConditionals, }, - { pathname: "/pascal/chapter-tuples", language: "PascaLIGO", name: "8 - Pascal - Tuples", data: pascalDataTuples }, - { pathname: "/pascal/chapter-records", language: "PascaLIGO", name: "9 - Pascal - Records", data: pascalDataRecords }, - { pathname: "/pascal/chapter-maps", language: "PascaLIGO", name: "10 - Pascal - Maps", data: pascalDataMaps }, - { pathname: "/pascal/chapter-lists", language: "PascaLIGO", name: "11 - Pascal - Lists", data: pascalDataLists }, + { pathname: '/pascal/chapter-tuples', language: 'PascaLIGO', name: '8 - Pascal - Tuples', data: pascalDataTuples }, + { pathname: '/pascal/chapter-records', language: 'PascaLIGO', name: '9 - Pascal - Records', data: pascalDataRecords }, + { pathname: '/pascal/chapter-maps', language: 'PascaLIGO', name: '10 - Pascal - Maps', data: pascalDataMaps }, + { pathname: '/pascal/chapter-lists', language: 'PascaLIGO', name: '11 - Pascal - Lists', data: pascalDataLists }, { - pathname: "/pascal/chapter-variants", - language: "PascaLIGO", - name: "12 - Pascal - Variants", + pathname: '/pascal/chapter-variants', + language: 'PascaLIGO', + name: '12 - Pascal - Variants', data: pascalDataVariant, }, { - pathname: "/pascal/chapter-main-function", - language: "PascaLIGO", - name: "13 - Pascal - Main function", + pathname: '/pascal/chapter-main-function', + language: 'PascaLIGO', + name: '13 - Pascal - Main function', data: pascalDataMainFunction, }, - { pathname: "/pascal/chapter-loops", language: "PascaLIGO", name: "14 - Pascal - Loops", data: pascalDataLoops }, + { pathname: '/pascal/chapter-loops', language: 'PascaLIGO', name: '14 - Pascal - Loops', data: pascalDataLoops }, { - pathname: "/pascal/chapter-addresses", - language: "PascaLIGO", - name: "15 - Pascal - Addresses", + pathname: '/pascal/chapter-addresses', + language: 'PascaLIGO', + name: '15 - Pascal - Addresses', data: pascalDataAddresses, }, { - pathname: "/pascal/chapter-built-ins", - language: "PascaLIGO", - name: "16 - Pascal - Built-ins", + pathname: '/pascal/chapter-built-ins', + language: 'PascaLIGO', + name: '16 - Pascal - Built-ins', data: pascalDataBuiltIns, }, { - pathname: "/pascal/chapter-transactions", - language: "PascaLIGO", - name: "17 - Pascal - Transactions", + pathname: '/pascal/chapter-transactions', + language: 'PascaLIGO', + name: '17 - Pascal - Transactions', data: pascalDataTransactions, }, { - pathname: "/pascal/chapter-timestamps", - language: "PascaLIGO", - name: "18 - Pascal - Timestamps", + pathname: '/pascal/chapter-timestamps', + language: 'PascaLIGO', + name: '18 - Pascal - Timestamps', data: pascalDataTimestamps, }, - { pathname: "/pascal/chapter-option", language: "PascaLIGO", name: "19 - Pascal - Option", data: pascalDataOption }, + { pathname: '/pascal/chapter-option', language: 'PascaLIGO', name: '19 - Pascal - Option', data: pascalDataOption }, { - pathname: "/pascal/chapter-interactions", - language: "PascaLIGO", - name: "20 - Pascal - Interactions", + pathname: '/pascal/chapter-interactions', + language: 'PascaLIGO', + name: '20 - Pascal - Interactions', data: pascalDataInteractions, }, { - pathname: "/pascal/chapter-polymorphism", - language: "PascalLIGO", - name: "21 - Pascal - Polymorphism", + pathname: '/pascal/chapter-polymorphism', + language: 'PascaLIGO', + name: '21 - Pascal - Polymorphism', data: pascalDataPolymorphism, }, { - pathname: "/pascal/chapter-lambda", - language: "PascaLIGO", - name: "22 - Pascal - Lambda", + pathname: '/pascal/chapter-lambda', + language: 'PascaLIGO', + name: '22 - Pascal - Lambda', data: pascalDataLambda, }, { - pathname: "/pascal/chapter-deploycontract", - language: "PascaLIGO", - name: "23 - Pascal - Deploy contract", + pathname: '/pascal/chapter-deploycontract', + language: 'PascaLIGO', + name: '23 - Pascal - Deploy contract', data: pascalDataDeployContract, }, { - pathname: "/pascal/chapter-multisig", - language: "PascaLIGO", - name: "24 - Pascal - Multisignature", + pathname: '/pascal/chapter-multisig', + language: 'PascaLIGO', + name: '24 - Pascal - Multisignature', data: pascalDataMultisig, }, { - pathname: "/pascal/chapter-fa12", - language: "PascaLIGO", - name: "25 - Pascal - FA12", + pathname: '/pascal/chapter-fa12', + language: 'PascaLIGO', + name: '25 - Pascal - FA12', data: pascalDataFA12, }, { - pathname: "/camel/chapter-about", - language: "CameLIGO", - name: "1 - Camel - About", + pathname: '/camel/chapter-about', + language: 'CameLIGO', + name: '1 - Camel - About', data: { course: undefined, exercise: undefined, solution: undefined }, }, - { pathname: "/camel/chapter-types", language: "CameLIGO", name: "2 - Camel - Types", data: camelDataTypes }, + { pathname: '/camel/chapter-types', language: 'CameLIGO', name: '2 - Camel - Types', data: camelDataTypes }, { - pathname: "/camel/chapter-variables", - language: "CameLIGO", - name: "3 - Camel - variables", + pathname: '/camel/chapter-variables', + language: 'CameLIGO', + name: '3 - Camel - variables', data: camelDataVariables, }, - { pathname: "/camel/chapter-math", language: "CameLIGO", name: "4 - Camel - Math", data: camelDataMath }, - { pathname: "/camel/chapter-strings", language: "CameLIGO", name: "5 - Camel - Strings", data: camelDataStrings }, + { pathname: '/camel/chapter-math', language: 'CameLIGO', name: '4 - Camel - Math', data: camelDataMath }, + { pathname: '/camel/chapter-strings', language: 'CameLIGO', name: '5 - Camel - Strings', data: camelDataStrings }, { - pathname: "/camel/chapter-functions", - language: "CameLIGO", - name: "6 - Camel - Functions", + pathname: '/camel/chapter-functions', + language: 'CameLIGO', + name: '6 - Camel - Functions', data: camelDataFunctions, }, { - pathname: "/camel/chapter-conditionals", - language: "CameLIGO", - name: "7 - Camel - Conditionals", + pathname: '/camel/chapter-conditionals', + language: 'CameLIGO', + name: '7 - Camel - Conditionals', data: camelDataConditionals, }, - { pathname: "/camel/chapter-tuples", language: "CameLIGO", name: "8 - Camel - Tuples", data: camelDataTuples }, - { pathname: "/camel/chapter-records", language: "CameLIGO", name: "9 - Camel - Records", data: camelDataRecords }, - { pathname: "/camel/chapter-maps", language: "CameLIGO", name: "10 - Camel - Maps", data: camelDataMaps }, - { pathname: "/camel/chapter-lists", language: "CameLIGO", name: "11 - Camel - Lists", data: camelDataLists }, - { pathname: "/camel/chapter-variants", language: "CameLIGO", name: "12 - Camel - Variants", data: camelDataVariant }, + { pathname: '/camel/chapter-tuples', language: 'CameLIGO', name: '8 - Camel - Tuples', data: camelDataTuples }, + { pathname: '/camel/chapter-records', language: 'CameLIGO', name: '9 - Camel - Records', data: camelDataRecords }, + { pathname: '/camel/chapter-maps', language: 'CameLIGO', name: '10 - Camel - Maps', data: camelDataMaps }, + { pathname: '/camel/chapter-lists', language: 'CameLIGO', name: '11 - Camel - Lists', data: camelDataLists }, + { pathname: '/camel/chapter-variants', language: 'CameLIGO', name: '12 - Camel - Variants', data: camelDataVariant }, { - pathname: "/camel/chapter-main-function", - language: "CameLIGO", - name: "13 - Camel - Main function", + pathname: '/camel/chapter-main-function', + language: 'CameLIGO', + name: '13 - Camel - Main function', data: camelDataMainFunction, }, - { pathname: "/camel/chapter-loops", language: "CameLIGO", name: "14 - Camel - Loops", data: camelDataLoops }, + { pathname: '/camel/chapter-loops', language: 'CameLIGO', name: '14 - Camel - Loops', data: camelDataLoops }, { - pathname: "/camel/chapter-addresses", - language: "CameLIGO", - name: "15 - Camel - Addresses", + pathname: '/camel/chapter-addresses', + language: 'CameLIGO', + name: '15 - Camel - Addresses', data: camelDataAddresses, }, { - pathname: "/camel/chapter-built-ins", - language: "CameLIGO", - name: "16 - Camel - Built-ins", + pathname: '/camel/chapter-built-ins', + language: 'CameLIGO', + name: '16 - Camel - Built-ins', data: camelDataBuiltIns, }, { - pathname: "/camel/chapter-transactions", - language: "CameLIGO", - name: "17 - Camel - Transactions", + pathname: '/camel/chapter-transactions', + language: 'CameLIGO', + name: '17 - Camel - Transactions', data: camelDataTransactions, }, { - pathname: "/camel/chapter-timestamps", - language: "CameLIGO", - name: "18 - Camel - Timestamps", + pathname: '/camel/chapter-timestamps', + language: 'CameLIGO', + name: '18 - Camel - Timestamps', data: camelDataTimestamps, }, - { pathname: "/camel/chapter-option", language: "CameLIGO", name: "19 - Camel - Option", data: camelDataOption }, + { pathname: '/camel/chapter-option', language: 'CameLIGO', name: '19 - Camel - Option', data: camelDataOption }, { - pathname: "/camel/chapter-interactions", - language: "CameLIGO", - name: "20 - Camel - Interactions", + pathname: '/camel/chapter-interactions', + language: 'CameLIGO', + name: '20 - Camel - Interactions', data: camelDataInteractions, }, { - pathname: "/camel/chapter-polymorphism", - language: "CameLIGO", - name: "21 - Camel - Polymorphism", + pathname: '/camel/chapter-polymorphism', + language: 'CameLIGO', + name: '21 - Camel - Polymorphism', data: camelDataPolymorphism, }, { - pathname: "/camel/chapter-lambda", - language: "CameLIGO", - name: "22 - Camel - Lambda", + pathname: '/camel/chapter-lambda', + language: 'CameLIGO', + name: '22 - Camel - Lambda', data: camelDataLambda, }, { - pathname: "/camel/chapter-deploycontract", - language: "CameLIGO", - name: "23 - Camel - Deploy contract", + pathname: '/camel/chapter-deploycontract', + language: 'CameLIGO', + name: '23 - Camel - Deploy contract', data: camelDataDeployContract, }, { - pathname: "/camel/chapter-multisig", - language: "CameLIGO", - name: "24 - Camel - Multisignature", + pathname: '/camel/chapter-multisig', + language: 'CameLIGO', + name: '24 - Camel - Multisignature', data: camelDataMultisig, }, { - pathname: "/camel/chapter-fa12", - language: "CameLIGO", - name: "25 - Camel - FA12", + pathname: '/camel/chapter-fa12', + language: 'CameLIGO', + name: '25 - Camel - FA12', data: camelDataFA12, }, { - pathname: "/reason/chapter-about", - language: "ReasonLIGO", - name: "1 - Reason - About", + pathname: '/reason/chapter-about', + language: 'ReasonLIGO', + name: '1 - Reason - About', data: { course: undefined, exercise: undefined, solution: undefined }, }, - { pathname: "/reason/chapter-types", language: "ReasonLIGO", name: "2 - Reason - Types", data: reasonDataTypes }, + { pathname: '/reason/chapter-types', language: 'ReasonLIGO', name: '2 - Reason - Types', data: reasonDataTypes }, { - pathname: "/reason/chapter-variables", - language: "ReasonLIGO", - name: "3 - Reason - variables", + pathname: '/reason/chapter-variables', + language: 'ReasonLIGO', + name: '3 - Reason - variables', data: reasonDataVariables, }, - { pathname: "/reason/chapter-math", language: "ReasonLIGO", name: "4 - Reason - Math", data: reasonDataMath }, + { pathname: '/reason/chapter-math', language: 'ReasonLIGO', name: '4 - Reason - Math', data: reasonDataMath }, { - pathname: "/reason/chapter-strings", - language: "ReasonLIGO", - name: "5 - Reason - Strings", + pathname: '/reason/chapter-strings', + language: 'ReasonLIGO', + name: '5 - Reason - Strings', data: reasonDataStrings, }, { - pathname: "/reason/chapter-functions", - language: "ReasonLIGO", - name: "6 - Reason - Functions", + pathname: '/reason/chapter-functions', + language: 'ReasonLIGO', + name: '6 - Reason - Functions', data: reasonDataFunctions, }, { - pathname: "/reason/chapter-conditionals", - language: "ReasonLIGO", - name: "7 - Reason - Conditionals", + pathname: '/reason/chapter-conditionals', + language: 'ReasonLIGO', + name: '7 - Reason - Conditionals', data: reasonDataConditionals, }, - { pathname: "/reason/chapter-tuples", language: "ReasonLIGO", name: "8 - Reason - Tuples", data: reasonDataTuples }, + { pathname: '/reason/chapter-tuples', language: 'ReasonLIGO', name: '8 - Reason - Tuples', data: reasonDataTuples }, { - pathname: "/reason/chapter-records", - language: "ReasonLIGO", - name: "9 - Reason - Records", + pathname: '/reason/chapter-records', + language: 'ReasonLIGO', + name: '9 - Reason - Records', data: reasonDataRecords, }, - { pathname: "/reason/chapter-maps", language: "ReasonLIGO", name: "10 - Reason - Maps", data: reasonDataMaps }, - { pathname: "/reason/chapter-lists", language: "ReasonLIGO", name: "11 - Reason - Lists", data: reasonDataLists }, + { pathname: '/reason/chapter-maps', language: 'ReasonLIGO', name: '10 - Reason - Maps', data: reasonDataMaps }, + { pathname: '/reason/chapter-lists', language: 'ReasonLIGO', name: '11 - Reason - Lists', data: reasonDataLists }, { - pathname: "/reason/chapter-variants", - language: "ReasonLIGO", - name: "12 - Reason - Variants", + pathname: '/reason/chapter-variants', + language: 'ReasonLIGO', + name: '12 - Reason - Variants', data: reasonDataVariant, }, { - pathname: "/reason/chapter-main-function", - language: "ReasonLIGO", - name: "13 - Reason - Main function", + pathname: '/reason/chapter-main-function', + language: 'ReasonLIGO', + name: '13 - Reason - Main function', data: reasonDataMainFunction, }, - { pathname: "/reason/chapter-loops", language: "ReasonLIGO", name: "14 - Reason - Loops", data: reasonDataLoops }, + { pathname: '/reason/chapter-loops', language: 'ReasonLIGO', name: '14 - Reason - Loops', data: reasonDataLoops }, { - pathname: "/reason/chapter-addresses", - language: "ReasonLIGO", - name: "15 - Reason - Addresses", + pathname: '/reason/chapter-addresses', + language: 'ReasonLIGO', + name: '15 - Reason - Addresses', data: reasonDataAddresses, }, { - pathname: "/reason/chapter-built-ins", - language: "ReasonLIGO", - name: "16 - Reason - Built-ins", + pathname: '/reason/chapter-built-ins', + language: 'ReasonLIGO', + name: '16 - Reason - Built-ins', data: reasonDataBuiltIns, }, { - pathname: "/reason/chapter-transactions", - language: "ReasonLIGO", - name: "17 - Reason - Transactions", + pathname: '/reason/chapter-transactions', + language: 'ReasonLIGO', + name: '17 - Reason - Transactions', data: reasonDataTransactions, }, { - pathname: "/reason/chapter-timestamps", - language: "ReasonLIGO", - name: "18 - Reason - Timestamps", + pathname: '/reason/chapter-timestamps', + language: 'ReasonLIGO', + name: '18 - Reason - Timestamps', data: reasonDataTimestamps, }, - { pathname: "/reason/chapter-option", language: "ReasonLIGO", name: "19 - Reason - Option", data: reasonDataOption }, + { pathname: '/reason/chapter-option', language: 'ReasonLIGO', name: '19 - Reason - Option', data: reasonDataOption }, { - pathname: "/reason/chapter-interactions", - language: "ReasonLIGO", - name: "20 - Reason - Interactions", + pathname: '/reason/chapter-interactions', + language: 'ReasonLIGO', + name: '20 - Reason - Interactions', data: reasonDataInteractions, }, { - pathname: "/reason/chapter-polymorphism", - language: "ReasonLIGO", - name: "21 - Reason - Polymorphism", + pathname: '/reason/chapter-polymorphism', + language: 'ReasonLIGO', + name: '21 - Reason - Polymorphism', data: reasonDataPolymorphism, }, { - pathname: "/reason/chapter-lambda", - language: "ReasonLIGO", - name: "22 - Reason - Lambda", + pathname: '/reason/chapter-lambda', + language: 'ReasonLIGO', + name: '22 - Reason - Lambda', data: reasonDataLambda, }, { - pathname: "/reason/chapter-deploycontract", - language: "ReasonLIGO", - name: "23 - Reason - Deploy contract", + pathname: '/reason/chapter-deploycontract', + language: 'ReasonLIGO', + name: '23 - Reason - Deploy contract', data: reasonDataDeployContract, }, { - pathname: "/reason/chapter-multisig", - language: "ReasonLIGO", - name: "24 - Reason - Multisignature", + pathname: '/reason/chapter-multisig', + language: 'ReasonLIGO', + name: '24 - Reason - Multisignature', data: reasonDataMultisig, }, { - pathname: "/reason/chapter-fa12", - language: "ReasonLIGO", - name: "25 - Reason - FA12", + pathname: '/reason/chapter-fa12', + language: 'ReasonLIGO', + name: '25 - Reason - FA12', data: reasonDataFA12, }, -]; +] diff --git a/src/frontend/src/pages/Chapter/Chapter.view.tsx b/src/frontend/src/pages/Chapter/Chapter.view.tsx index dabc19a..1135ad0 100644 --- a/src/frontend/src/pages/Chapter/Chapter.view.tsx +++ b/src/frontend/src/pages/Chapter/Chapter.view.tsx @@ -3,10 +3,10 @@ import Markdown from 'markdown-to-jsx' import * as PropTypes from 'prop-types' import * as React from 'react' -import { CardBottomCorners, CardTopCorners } from './Chapter.components/Card/Card.style' import { PENDING, RIGHT, WRONG } from '../Chapters/Pascal/ChapterAbout/ChapterAbout.constants' //prettier-ignore import { Button, ButtonBorder, ButtonText, ChapterCourse, ChapterGrid, ChapterH1, ChapterH2, ChapterItalic, ChapterMonaco, ChapterStyled, ChapterValidator, ChapterValidatorContent, ChapterValidatorContentWrapper, ChapterValidatorInside, ChapterValidatorTitle } from "../Chapters/Pascal/ChapterAbout/ChapterAbout.style"; +import { CardBottomCorners, CardTopCorners } from './Chapter.components/Card/Card.style' import { Dialog } from './Chapter.components/Dialog/Dialog.controller' import { Light } from './Chapter.components/Light/Light.view' @@ -27,7 +27,7 @@ const MonacoReadOnly = ({ children }: any) => { folding: false, readOnly: true, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', }} /> @@ -51,7 +51,7 @@ const MonacoEditor = ({ proposedSolution, proposedSolutionCallback }: any) => { folding: true, readOnly: false, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', }} /> @@ -76,7 +76,7 @@ const MonacoDiff = ({ solution, proposedSolution }: any) => { folding: true, readOnly: false, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', renderSideBySide: false, }} /> diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterAbout/ChapterAbout.view.tsx b/src/frontend/src/pages/Chapters/Camel/ChapterAbout/ChapterAbout.view.tsx index a2485c2..ba7ca48 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterAbout/ChapterAbout.view.tsx +++ b/src/frontend/src/pages/Chapters/Camel/ChapterAbout/ChapterAbout.view.tsx @@ -18,10 +18,10 @@ monaco base: 'vs-dark', inherit: true, rules: [ - { token: 'comment', foreground: '666666', fontStyle: 'italic' }, - { token: 'keyword', foreground: 'FF5A00' }, - { token: 'number', foreground: '00FF47' }, - { token: 'string', foreground: 'FA00FF' }, + { token: 'comment', foreground: '#42edf8', fontStyle: 'italic' }, + { token: 'keyword', foreground: '#FF5A00' }, + { token: 'number', foreground: '#00FF47' }, + { token: 'string', foreground: '#FA00FF' }, ], colors: { 'editor.foreground': '#F8F8F8', @@ -51,7 +51,7 @@ const MonacoReadOnly = ({ height, value }: any) => { folding: false, readOnly: true, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', }} /> diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterAbout/ChapterAbout.view.tsx b/src/frontend/src/pages/Chapters/Pascal/ChapterAbout/ChapterAbout.view.tsx index a2485c2..ba7ca48 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterAbout/ChapterAbout.view.tsx +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterAbout/ChapterAbout.view.tsx @@ -18,10 +18,10 @@ monaco base: 'vs-dark', inherit: true, rules: [ - { token: 'comment', foreground: '666666', fontStyle: 'italic' }, - { token: 'keyword', foreground: 'FF5A00' }, - { token: 'number', foreground: '00FF47' }, - { token: 'string', foreground: 'FA00FF' }, + { token: 'comment', foreground: '#42edf8', fontStyle: 'italic' }, + { token: 'keyword', foreground: '#FF5A00' }, + { token: 'number', foreground: '#00FF47' }, + { token: 'string', foreground: '#FA00FF' }, ], colors: { 'editor.foreground': '#F8F8F8', @@ -51,7 +51,7 @@ const MonacoReadOnly = ({ height, value }: any) => { folding: false, readOnly: true, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', }} /> diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterAbout/ChapterAbout.view.tsx b/src/frontend/src/pages/Chapters/Reason/ChapterAbout/ChapterAbout.view.tsx index a2485c2..ba7ca48 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterAbout/ChapterAbout.view.tsx +++ b/src/frontend/src/pages/Chapters/Reason/ChapterAbout/ChapterAbout.view.tsx @@ -18,10 +18,10 @@ monaco base: 'vs-dark', inherit: true, rules: [ - { token: 'comment', foreground: '666666', fontStyle: 'italic' }, - { token: 'keyword', foreground: 'FF5A00' }, - { token: 'number', foreground: '00FF47' }, - { token: 'string', foreground: 'FA00FF' }, + { token: 'comment', foreground: '#42edf8', fontStyle: 'italic' }, + { token: 'keyword', foreground: '#FF5A00' }, + { token: 'number', foreground: '#00FF47' }, + { token: 'string', foreground: '#FA00FF' }, ], colors: { 'editor.foreground': '#F8F8F8', @@ -51,7 +51,7 @@ const MonacoReadOnly = ({ height, value }: any) => { folding: false, readOnly: true, fontSize: 14, - fontFamily: 'Roboto', + fontFamily: 'Proxima Nova', }} /> diff --git a/src/frontend/src/pages/User/User.actions.tsx b/src/frontend/src/pages/User/User.actions.tsx new file mode 100644 index 0000000..98f9d68 --- /dev/null +++ b/src/frontend/src/pages/User/User.actions.tsx @@ -0,0 +1,25 @@ +import { store } from 'index' +import { GetPublicUserInputs } from 'shared/page/GetPublicUser' + +export const GET_USER_REQUEST = 'GET_USER_REQUEST' +export const GET_USER_COMMIT = 'GET_USER_COMMIT' +export const GET_USER_ROLLBACK = 'GET_USER_ROLLBACK' + +export const getUser = ({ username }: GetPublicUserInputs) => (dispatch: any) => { + dispatch({ + type: GET_USER_REQUEST, + payload: { username }, + meta: { + offline: { + effect: { + url: `${process.env.REACT_APP_BACKEND_URL}/page/get-user`, + method: 'POST', + headers: { Authorization: `Bearer ${store.getState().auth.jwt}` }, + json: { username }, + }, + commit: { type: GET_USER_COMMIT, meta: { username } }, + rollback: { type: GET_USER_ROLLBACK, meta: { username } }, + }, + }, + }) +} diff --git a/src/frontend/src/pages/User/User.controller.tsx b/src/frontend/src/pages/User/User.controller.tsx new file mode 100644 index 0000000..04f46b3 --- /dev/null +++ b/src/frontend/src/pages/User/User.controller.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { State } from 'reducers' + +import { getUser } from './User.actions' +import { UserView } from './User.view' +import { useParams } from 'react-router-dom' +import { PublicUser } from 'shared/user/PublicUser' + +export const User = () => { + const dispatch = useDispatch() + const loading = useSelector((state: State) => state.user.loading) + let { username } = useParams() + const user = useSelector((state: State) => (state.user as Record)[username]) + + useEffect(() => { + dispatch(getUser({ username })) + }, [dispatch, username]) + + return +} diff --git a/src/frontend/src/pages/User/User.reducers.tsx b/src/frontend/src/pages/User/User.reducers.tsx new file mode 100644 index 0000000..80c1eff --- /dev/null +++ b/src/frontend/src/pages/User/User.reducers.tsx @@ -0,0 +1,35 @@ +import { GET_USER_COMMIT, GET_USER_REQUEST, GET_USER_ROLLBACK } from './User.actions' + +export interface UserState { + loading?: boolean +} + +const userState: UserState = { + loading: false, +} + +export function user(state = userState, action: any): UserState { + switch (action.type) { + case GET_USER_REQUEST: { + return { + ...state, + loading: true, + } + } + case GET_USER_COMMIT: { + return { + ...state, + loading: false, + [action.meta.username]: action.payload.user, + } + } + case GET_USER_ROLLBACK: { + return { + ...state, + loading: false, + } + } + default: + return state + } +} diff --git a/src/frontend/src/pages/User/User.style.tsx b/src/frontend/src/pages/User/User.style.tsx new file mode 100644 index 0000000..dcf47bb --- /dev/null +++ b/src/frontend/src/pages/User/User.style.tsx @@ -0,0 +1,3 @@ +import styled from 'styled-components/macro' + +export const UserStyled = styled.div`` diff --git a/src/frontend/src/pages/User/User.view.tsx b/src/frontend/src/pages/User/User.view.tsx new file mode 100644 index 0000000..eb44305 --- /dev/null +++ b/src/frontend/src/pages/User/User.view.tsx @@ -0,0 +1,28 @@ +import * as PropTypes from 'prop-types' +import * as React from 'react' +import { PublicUser } from 'shared/user/PublicUser' + +// prettier-ignore +import { UserStyled } from './User.style' + +type UserViewProps = { + loading: boolean + user: PublicUser +} + +export const UserView = ({ loading, user }: UserViewProps) => { + return {user.progress} +} + +UserView.propTypes = { + loading: PropTypes.bool, + user: PropTypes.object, +} + +UserView.defaultProps = { + loading: false, + user: { + username: 'Not found', + karmaTotal: 0, + }, +} diff --git a/src/frontend/src/reducers/index.ts b/src/frontend/src/reducers/index.ts index 1788e98..0c54c14 100644 --- a/src/frontend/src/reducers/index.ts +++ b/src/frontend/src/reducers/index.ts @@ -2,6 +2,7 @@ import { connectRouter } from 'connected-react-router' import { changePassword, ChangePasswordState } from 'pages/ChangePassword/ChangePassword.reducers' import { forgotPassword, ForgotPasswordState } from 'pages/ForgotPassword/ForgotPassword.reducers' import { resetPassword, ResetPasswordState } from 'pages/ResetPassword/ResetPassword.reducers' +import { user, UserState } from 'pages/User/User.reducers' import { combineReducers } from 'redux' import { drawer, DrawerState } from '../app/App.components/Drawer/Drawer.reducers' @@ -20,10 +21,12 @@ export const reducers = (history: any) => forgotPassword, resetPassword, changePassword, - serviceWorker + serviceWorker, + user }) export interface State { + user: UserState toaster: ToasterState auth: AuthState drawer: DrawerState diff --git a/src/frontend/src/shared/captcha/Captcha.ts b/src/frontend/src/shared/captcha/Captcha.ts index 06935c4..206226d 100644 --- a/src/frontend/src/shared/captcha/Captcha.ts +++ b/src/frontend/src/shared/captcha/Captcha.ts @@ -7,6 +7,7 @@ import { Property, Index, getModel } from '../../helpers/typegoose' import { CaptchaFor } from './CaptchaFor' @Index({ userId: 1, captchaFor: 1 }, { unique: true }) +@Index({ expireAt: 1 }, { expireAfterSeconds: 0 }) export class Captcha { @IsMongoId() readonly _id!: ObjectId @@ -42,7 +43,6 @@ export class Captcha { @Property({ required: true, default: DayJs().add(1, 'hour').toDate(), - expires: '1h', }) @IsDate() expiresAt!: Date diff --git a/src/frontend/src/shared/user/GetPublicUser.ts b/src/frontend/src/shared/page/GetPublicUser.ts similarity index 87% rename from src/frontend/src/shared/user/GetPublicUser.ts rename to src/frontend/src/shared/page/GetPublicUser.ts index a5f8e7f..95ff90b 100644 --- a/src/frontend/src/shared/user/GetPublicUser.ts +++ b/src/frontend/src/shared/page/GetPublicUser.ts @@ -1,6 +1,6 @@ import { Length, Matches } from 'class-validator' import { Expose } from 'class-transformer' -import { PublicUser } from './PublicUser' +import { PublicUser } from '../user/PublicUser' export class GetPublicUserInputs { @Expose() diff --git a/src/frontend/src/shared/quota/Quota.ts b/src/frontend/src/shared/quota/Quota.ts index 175b8d0..4b5f943 100644 --- a/src/frontend/src/shared/quota/Quota.ts +++ b/src/frontend/src/shared/quota/Quota.ts @@ -6,6 +6,7 @@ import { Property, Index, getModel } from '../../helpers/typegoose' import { QuotaType } from './QuotaType' @Index({ userId: 1, quotaType: 1 }, { unique: true }) +@Index({ expireAt: 1 }, { expireAfterSeconds: 0 }) export class Quota { @IsMongoId() readonly _id!: ObjectId @@ -27,7 +28,6 @@ export class Quota { @Property({ required: true, default: DayJs().add(24, 'hour').toDate(), - expires: '24h', }) @IsDate() expiresAt!: Date diff --git a/src/frontend/src/shared/user/AddProgress.ts b/src/frontend/src/shared/user/AddProgress.ts new file mode 100644 index 0000000..02488fd --- /dev/null +++ b/src/frontend/src/shared/user/AddProgress.ts @@ -0,0 +1,15 @@ +import { Expose } from 'class-transformer' +import { Length, Matches } from 'class-validator' + +import { PublicUser } from '../user/PublicUser' + +export class AddProgressInputs { + @Expose() + @Length(2, 100) + @Matches(/^[a-zA-Z0-9-\/]*$/, { message: 'Chapter slug can only contain letters, numbers, dashes and slashes' }) + chapterDone!: string +} + +export class AddProgressOutputs { + user!: PublicUser +} diff --git a/src/frontend/src/shared/user/PublicUser.ts b/src/frontend/src/shared/user/PublicUser.ts index b410e52..3387c27 100644 --- a/src/frontend/src/shared/user/PublicUser.ts +++ b/src/frontend/src/shared/user/PublicUser.ts @@ -1,8 +1,6 @@ -import { IsDate, IsEmail, IsEnum, IsInt, IsMongoId, Length, Matches } from 'class-validator' +import { IsArray, IsDate, IsEmail, IsMongoId, Length, Matches } from 'class-validator' import { ObjectId } from 'mongodb' -import { UserRole } from './UserRole' - export class PublicUser { @IsMongoId() readonly _id!: ObjectId @@ -14,11 +12,8 @@ export class PublicUser { @IsEmail() emailVerified?: boolean - @IsInt() - karmaTotal?: number - - @IsEnum(UserRole) - userRole?: UserRole + @IsArray() + progress?: string[] @IsDate() createdAt!: Date diff --git a/src/frontend/src/shared/user/User.ts b/src/frontend/src/shared/user/User.ts index 0243426..ebe7332 100644 --- a/src/frontend/src/shared/user/User.ts +++ b/src/frontend/src/shared/user/User.ts @@ -25,6 +25,9 @@ export class User { @Property({ required: true }) hashedPassword!: string + @Property({ nullable: true, optional: true }) + progress?: string[] + @IsDate() createdAt!: Date