diff --git a/.gitignore b/.gitignore index d8fd15f..e1c7baf 100644 --- a/.gitignore +++ b/.gitignore @@ -73,8 +73,6 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties -.idea/httpRequests -.idea/caches/build_file_checksums.ser .DS_Store .AppleDouble .LSOverride diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index c167e99..ed98a75 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -3,27 +3,41 @@ ## Authentication - As a client, I want to log in to the service. - - [ ] In the front-end, the client provides a username, and a password to request login to the back-end. - - [ ] In the back-end, the server processes the login request from the front-end. + - [X] In the front-end, the client provides a username, and a password to request login to the back-end. + - [X] In the back-end, the server processes the login request from the front-end. - As a client, I want to log out from the service. - - [ ] In the front-end, the client requests logout to the backend. - - [ ] In the back-end, the server processes the logout request from the front-end. + - [X] In the front-end, the client requests logout to the backend. + - [X] In the back-end, the server processes the logout request from the front-end. ## Ringtone - As a client, I want to download a ringtone. - [ ] In the front-end, the client provides a ringtone ID to request a ringtone from the back-end. - [ ] In the back-end, the server sends the ringtone data to the front-end. -- As a client, I want to search for composers and ringtones. - - [ ] In the front-end, the client requests provides a search keyword to request for composers and ringtones from the back-end. - - [ ] In the back-end, the server retrieves the composers and ringtones whose name matches the search keyword provided - by the front-end. - As a client, I want to browse ringtones. - - [ ] In the front-end, the client provides an optional skip and take arguments to request multiple ringtones from the back-end. + - [ ] In the front-end, the client provides an optional skip and take arguments to request multiple ringtones from the + back-end. - [X] In the back-end, the server sends the ringtones to the front-end. +- As a client, I want to view the ringtones made by a composer. + - [ ] In the front-end + - [ ] In the back-end + +## Search + +- As a client, I want to search for ringtones. + - [ ] In the front-end, the client requests provides a search keyword to request for ringtones from the back-end. + - [X] In the back-end, the server retrieves the ringtones whose name matches the search keyword provided by the + front-end. +- As a client, I want to search for composers. + - [ ] In the front-end, the client requests provides a search keyword to request for composers from the back-end. + - [ ] In the back-end, the server retrieves the composers whose name matches the search keyword provided by the + front-end. + +## Composer + - As a composer, I want to create a ringtone. - - [ ] In the front-end, the client inputs ringtone data to the view. - - [ ] In the front-end, the client sends the ringtone data to the back-end. + - [X] In the front-end, the client inputs ringtone data to the view. + - [X] In the front-end, the client sends the ringtone data to the back-end. - [X] In the back-end, the server stores the ringtone data. - As a composer, I want to update a ringtone. - [ ] In the front-end, the client modifies the ringtone data retrieved from the back-end and loaded on the view. @@ -37,4 +51,5 @@ - [X] In the back-end, the server untags a ringtone as deleted if its ID matches the one provided by the front-end. - As a composer, I want to hard-delete of a ringtone. - [ ] In the front-end, the client provides a ringtone ID to request a ringtone's deletion rollback to the back-end. - - [X] In the back-end, the server removes a ringtone in the database if its ID matches the one provided by the front-end. + - [X] In the back-end, the server removes a ringtone in the database if its ID matches the one provided by the + front-end. diff --git a/packages/app-web/cypress.json b/packages/app-web/cypress.json index 0967ef4..979d4d0 100644 --- a/packages/app-web/cypress.json +++ b/packages/app-web/cypress.json @@ -1 +1,3 @@ -{} +{ + "baseUrl": "http://localhost:3000" +} diff --git a/packages/app-web/cypress/fixtures/example.json b/packages/app-web/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/packages/app-web/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/packages/app-web/cypress/integration/login.ts b/packages/app-web/cypress/integration/login.ts index 0ed8bc3..eb219da 100644 --- a/packages/app-web/cypress/integration/login.ts +++ b/packages/app-web/cypress/integration/login.ts @@ -1,5 +1,26 @@ describe('login', () => { + beforeEach(() => { + cy + .visit('/api/auth/logout') + }) + it('should log the current user in', () => { - cy.visit('http://localhost:3000') + cy + .visit('/api/auth/login') + + cy + .get('input[name="email"]') + .type(Cypress.env('USERNAME')) + + cy + .get('input[name="password"]') + .type(Cypress.env('PASSWORD')) + + cy + .get('form') + .submit() + .wait(3000) + .location('href') + .should('equal', 'http://localhost:3000') }) }) diff --git a/packages/app-web/cypress/integration/ringtone.ts b/packages/app-web/cypress/integration/ringtone.ts new file mode 100644 index 0000000..1eeb3df --- /dev/null +++ b/packages/app-web/cypress/integration/ringtone.ts @@ -0,0 +1,22 @@ +describe('ringtone', () => { + beforeEach(() => { + cy.visit('/my/create/ringtone') + }) + + it('should be created', () => { + cy.intercept('POST', new URL('/api/ringtones', Cypress.env('API_BASE_URL')).toString()).as('request') + cy + .get('input[name="name"]') + .type('Happy Birthday') + + cy + .get('textarea[name="data"]') + .type('8g1 8g1 4a1 4g1 4c2 2b1 8g1 8g1 4a1 4g1 4d2 2c2 8g1 8g1 4g2 4e2 8c2 8c2 4b1 4a1 8f2 8f2 4e2 4c2 4d2 2c2') + + cy + .get('form[aria-label="Create Ringtone"]') + .submit() + + cy.wait('@request').its('response.statusCode').should('equal', 201) + }) +}) diff --git a/packages/app-web/cypress/plugins/index.ts b/packages/app-web/cypress/plugins/index.ts index 1b63c6e..9e6b4ee 100644 --- a/packages/app-web/cypress/plugins/index.ts +++ b/packages/app-web/cypress/plugins/index.ts @@ -12,6 +12,8 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) +import dotenvPlugin = require('cypress-dotenv') + /** * @type {Cypress.PluginConfig} */ @@ -19,4 +21,7 @@ export default (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config + + config = dotenvPlugin(config) + return config } diff --git a/packages/app-web/package.json b/packages/app-web/package.json index 4620dcf..fcd12f6 100644 --- a/packages/app-web/package.json +++ b/packages/app-web/package.json @@ -10,6 +10,7 @@ "e2e": "cypress" }, "dependencies": { + "@auth0/nextjs-auth0": "^1.3.1", "@tesseract-design/viewfinder": "^0.1.1", "@theoryofnekomata/formxtr": "^0.1.2", "next": "10.2.0", @@ -25,6 +26,8 @@ "@types/react": "^17.0.5", "@types/styled-components": "^5.1.9", "cypress": "^7.3.0", + "cypress-dotenv": "^1.2.2", + "dotenv": "^9.0.2", "jest": "^26.6.3", "ts-jest": "^26.5.6", "typescript": "^4.2.4" diff --git a/packages/app-web/src/components/molecules/brand/Brand/index.tsx b/packages/app-web/src/components/molecules/brand/Brand/index.tsx new file mode 100644 index 0000000..2921caf --- /dev/null +++ b/packages/app-web/src/components/molecules/brand/Brand/index.tsx @@ -0,0 +1,41 @@ +import styled from 'styled-components' +import Link from '../../navigation/Link' + +const BrandBase = styled(Link)({ + display: 'block', + textDecoration: 'none', + fontSize: '1.5rem', + fontWeight: 'bold', + fontStretch: '75%', + textTransform: 'uppercase', + width: '2rem', + textAlign: 'center', + '@media (min-width: 720px)': { + width: '8rem', + textAlign: 'left', + }, +}) + +const Hide = styled('span')({ + display: 'none', + '@media (min-width: 720px)': { + display: 'inline', + }, +}) + +const Brand = () => { + return ( + + T + + onality + + + ) +} + +export default Brand diff --git a/packages/app-web/src/components/molecules/forms/TextArea/index.tsx b/packages/app-web/src/components/molecules/forms/TextArea/index.tsx index 7d27828..1b330c1 100644 --- a/packages/app-web/src/components/molecules/forms/TextArea/index.tsx +++ b/packages/app-web/src/components/molecules/forms/TextArea/index.tsx @@ -2,7 +2,6 @@ import {FC} from 'react' import styled from 'styled-components' const Base = styled('div')({ - height: '3rem', borderRadius: '0.25rem', overflow: 'hidden', position: 'relative', @@ -34,9 +33,9 @@ const Label = styled('span')({ const Input = styled('textarea')({ display: 'block', width: '100%', - height: '100%', + minHeight: '3rem', margin: 0, - padding: '0 1rem', + padding: '1rem 1rem', boxSizing: 'border-box', font: 'inherit', border: 0, @@ -51,6 +50,7 @@ type Props = { className?: string, block?: boolean, placeholder?: string, + defaultValue?: string | number | readonly string[], } const TextArea: FC = ({ diff --git a/packages/app-web/src/components/molecules/forms/TextInput/index.tsx b/packages/app-web/src/components/molecules/forms/TextInput/index.tsx index e3f5e29..fd6576d 100644 --- a/packages/app-web/src/components/molecules/forms/TextInput/index.tsx +++ b/packages/app-web/src/components/molecules/forms/TextInput/index.tsx @@ -51,6 +51,7 @@ type Props = { className?: string, block?: boolean, placeholder?: string, + defaultValue?: string | number | readonly string[], type?: 'email' | 'url' | 'text' | 'tel', } diff --git a/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx b/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx index 75e1ae6..7a28f17 100644 --- a/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx +++ b/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx @@ -1,4 +1,5 @@ import {FC, FormEventHandler} from 'react' +import {models} from '@tonality/library-common' import styled from 'styled-components' import TextInput from '../../../molecules/forms/TextInput' import TextArea from '../../../molecules/forms/TextArea' @@ -13,12 +14,14 @@ type Props = { onSubmit?: FormEventHandler, action?: string, labels: Record, + defaultValues?: Partial, } const CreateRingtoneForm: FC = ({ onSubmit, action, labels, + defaultValues = {}, }) => { return (
= ({ action={action} aria-label={labels['form']} > + { + typeof (defaultValues.id as unknown) === 'string' + && ( + + ) + } +