Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql
NEXT_PUBLIC_GRAPHQL_SECRET=localadmin
60 changes: 60 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"airbnb",
"airbnb-typescript"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module",
"project": [
"./tsconfig.json"
]
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
],
"react/require-default-props": "off",
"no-console": "off",
"react/button-has-type": "off",
"import/prefer-default-export": "off",
"react/no-array-index-key": "off",
"jsx-a11y/anchor-is-valid": "off",
"react/jsx-props-no-spreading": "off",
"react/prop-types": "off",
// suppress errors for missing 'import React' in files
"react/react-in-jsx-scope": "off",
// allow jsx syntax in js files (for next.js project)
"react/jsx-filename-extension": [
1,
{
"extensions": [
".js",
".jsx",
".ts",
".tsx"
]
}
]
}
}
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
13 changes: 13 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp

// List of extensions which should be recommended for users of this workspace.
"recommendations": [

],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [

]
}
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"reactSnippets.settings.prettierEnabled": true,
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
}
68 changes: 24 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
## Front End Code Challenge
## How to setup

This is Limbic's React/React Native Coding Challenge which will allow us to get a glimpse into our candidates' overall developer skills.
This project uses postgres db for storage and [hasura](https://hasura.io/) as graphql engine.
There is a `docker-compose` file in `db` folder which will prepare backend. Please run the following in you r terminal

You are free to create a React web app, or a React Native mobile app, it's up to you. We'd rather you use something you are comfortable with so that time isn't wasted on project setup and we can get a real feel for your coding patterns and understanding of best practices with React and/or React Native, regardless of the end use case.
cd db
docker-compose up -d
2 new containers will be created for postgres and hasura. To verify every thing is working please visit http://localhost:8080/console

### Instructions
To create schema and seed tables run this command (make sure you are still in db folder)

1. **Submitting Code:**
hasura migrate apply
and then

Option A:
- Fork this repo
- Issue a Pull Request when you're ready to start. This will count as your starting date.
- Setup your development environment for React or React Native
- Implement your solution
- Commit your changes into the forked repo
hasura metadata apply

Option B:
- Setup your development environment for React or React Native
- Implement your solution
- Archive your solution into a zip file
- Send us the zip file. We should be able to extract the content and run it from there (w/o node_modules)
Now you have to be able to see `questions` and `answers` tables in hasura http://localhost:8080/console/data/default/schema/public

2. **Deadline:**
to run the project please navigate to root folder and

You have 1 week to complete as much tasks as you can from the challenge below. Countdown starts from the date you issued the PR or from the date you were invited to complete this challenge via email.
npm run dev

3. **Implementation:**
## Stack and tools
Some highlights about implementation

There is no correct way to do it, you are free to use whatever libraries you like. We want to see what you come up with on your own.
1. This is a Next.js project. Pages are rendered client side.
2. Tailwind css for styling. There are many great tools out there for styling a react project but I found tailwind quick and handy for this project. I'm experienced working with [styled components](https://styled-components.com/) as well
3. TypeScript for type safety
4. ESLint to maintain code style
5. [Apollo client](https://www.apollographql.com/docs/react) for graphql client
6. [react hook form](https://react-hook-form.com/) to simplify working with forms
7. No usage of 3d party UI libraries to show case some simple ui component implementation

### The Challenge

Jane is a clinical therapist and wants her clients to answer simple questionnaires in order to better understand them. She needs a way to add/delete/edit questions and also see the answers of each client.

### Requirements

Your app should be able to complete the following tasks:

- See a list of questions
- Add a new question
- Edit a question
- Delete a question
- See a list of clients
- See a client's answers

### Bonus Points

The following tasks will **NOT** have a negative impact in how well you did, but you will get bonus points for completing any of them.

- Ability to persist data locally
- Ability to select the type of answer a question will have. Types like free text, single choice from a predefined list, multiple choice from a predefined list, etc.
- Ability for the app to answer the questions. Basically using a fake login it could determine if the user is a client and display the questionnaire they need to answer. (fake login can be two buttons chosing the type of user

Good Luck!
Here is a quick screen capture of project
https://drive.google.com/file/d/18cXAP4NBL7Q1BRlU8rZY2mNl9HUMLYIk/view?usp=sharing
21 changes: 21 additions & 0 deletions apollo-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
uri: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
headers: {
'x-hasura-admin-secret': process.env.NEXT_PUBLIC_GRAPHQL_SECRET,
},
cache: new InMemoryCache({
addTypename: false,
}),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
},
query: {
fetchPolicy: 'no-cache',
},
},
});

export default client;
85 changes: 85 additions & 0 deletions components/answers/form/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useMutation } from '@apollo/client';
import { useForm } from 'react-hook-form';
import { useState } from 'react';
import FormLabel from '../../ui/FormLabel';
import TextInput from '../../ui/textInput';
import Button from '../../ui/button';
import { INSERT_ANSWER } from './query';
import { prepareDefaultValues } from './helpers';
import { Answer } from '../../../types/answer';
import { Question } from '../../../types/question';

interface AnswerFormProps {
answers?: Answer[];
questions?: Question[];
}

export default function AnswerForm({ answers, questions }: AnswerFormProps) {
const [saveSuccess, setSaveSuccess] = useState(false);
const defaultValues = prepareDefaultValues(answers);

const {
register, handleSubmit, formState: { errors },
} = useForm({ defaultValues });

const [insertAnswer] = useMutation(INSERT_ANSWER);

const onSubmit = async (data) => {
console.log('data', data);

const preparedAnswers = [];
Object.keys(data).forEach((key) => {
const questionId = key.split('_')[1];
preparedAnswers.push({ question_id: questionId, answer: data[key] });
});

await insertAnswer({ variables: { answers: preparedAnswers } });
setSaveSuccess(true);
};

return (
<form className="bg-white rounded p-4" onSubmit={handleSubmit(onSubmit)}>
{questions.map((question) => (
<div key={question.id}>
<FormLabel htmlFor={`q_${question.id}`} label={question.text} />

{question.type === 'number' && (
<TextInput
name={`q_${question.id}`}
register={register}
isNumber
/>
)}

{question.type === 'text' && (
<TextInput
name={`q_${question.id}`}
register={register}
/>
)}

{question.type === 'multiLine' && (
<TextInput
multiLine
name={`q_${question.id}`}
register={register}
minLength={10}
/>
)}
<p className="text-red-500">
{errors[`q_${question.id}`] && <span>{errors[`q_${question.id}`].message}</span>}
</p>
</div>

))}
<div className="mb-12">
<Button
type="submit"
text="Submit"
/>
{saveSuccess && <p className="text-green-500">Saved successfully</p>}

</div>
</form>
);
}
7 changes: 7 additions & 0 deletions components/answers/form/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const prepareDefaultValues = (answers) => {
const defaultValues = {};
answers.forEach((answer) => {
defaultValues[`q_${answer.question_id}`] = answer.answer;
});
return defaultValues;
};
24 changes: 24 additions & 0 deletions components/answers/form/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { gql } from '@apollo/client';

export const GET_ANSWERS = gql`
query GetAnswers {
questions {
id
type
text
}
answers {
id
question_id
answer
}
}
`;

export const INSERT_ANSWER = gql`
mutation InsertAnswer($answers: [answers_insert_input!]!) {
insert_answers(objects: $answers, on_conflict: {constraint: answers_question_id_key, update_columns: [answer]}) {
affected_rows
}
}
`;
7 changes: 7 additions & 0 deletions components/layout/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Footer() {
return (
<div className="p-2 bg-gray-300 text-center fixed bottom-0 w-full">
<p>Limbic code challenge</p>
</div>
);
}
28 changes: 28 additions & 0 deletions components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactNode } from 'react';

import Head from 'next/head';

import Navbar from './navbar';
import Footer from './footer';

export default function Layout({ children } : ReactNode) {
return (
<>
<div className="flex flex-col h-screen justify-between">
<Head>
<title>Limbic code challenge </title>
<meta name="description" content="Limbic code challenge" />
<link rel="icon" href="/favicon.ico" />
</Head>

<Navbar />

<main className="mb-10 h-full p-5 bg-gray-100">{children}</main>

<Footer />
</div>
<div />

</>
);
}
Loading