├── .dockerignore ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── node.yaml ├── .gitignore ├── .history ├── package_20230114161045.json └── package_20230114163606.json ├── Dockerfile ├── LICENSE.md ├── README.md ├── architecture ├── backend.dio └── backend.png ├── client ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── README.md ├── jest.config.js ├── jest.setup.js ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── hello.ts │ └── index.tsx ├── public │ ├── assets │ │ ├── images │ │ │ ├── Tiktok.svg │ │ │ ├── facebookimg.svg │ │ │ ├── instagramimg.svg │ │ │ ├── snapchatimg.svg │ │ │ └── twitterimg.svg │ │ ├── people_alias.png │ │ ├── people_alias2.svg │ │ └── people_alias3.png │ ├── favicon.ico │ ├── intro │ │ ├── left.svg │ │ ├── right.svg │ │ └── yellow.svg │ └── vercel.svg ├── src │ ├── components │ │ ├── ComingSoon │ │ │ ├── ComingSoon.tsx │ │ │ └── styles.ts │ │ ├── Footer │ │ │ ├── Newsletter │ │ │ │ ├── index.tsx │ │ │ │ └── styles.ts │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Hint │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Navbar │ │ │ ├── Drawer │ │ │ │ ├── Drawer.tsx │ │ │ │ └── styles.ts │ │ │ ├── Switcher │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Search │ │ │ ├── SearchBox │ │ │ │ ├── SearchBox.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.ts │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── SearchResult │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ └── intro │ │ │ ├── Intro.tsx │ │ │ └── styles.ts │ ├── interfaces │ │ └── index.ts │ └── utils │ │ ├── Layout.tsx │ │ ├── Link.tsx │ │ ├── PageProvider.tsx │ │ ├── createEmotionCache.ts │ │ ├── data.ts │ │ ├── theme.ts │ │ ├── userInfoContext.tsx │ │ └── userInfoReducer.tsx ├── tsconfig.json └── types.d.ts ├── docker-compose.yml ├── jest.config.ts ├── package-lock.json ├── package.json ├── server ├── app.ts ├── controllers │ ├── Alias.test.ts │ ├── Alias.ts │ └── Healthcheck.ts ├── platforms │ ├── platform.ts │ ├── twitter │ │ └── twitter.ts │ └── youtube │ │ └── youtube.ts ├── routes │ └── Api.ts ├── server.ts └── tests │ └── Alias.test.ts ├── tsconfig.build.json └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | *.md 3 | node_modules 4 | build 5 | client 6 | .github 7 | dist 8 | .env -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | TWITTER_BEARER_TOKEN="" 3 | YOUTUBE_API_KEY="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "rules": { //For list of rules, https://eslint.org/docs/latest/rules/ 13 | "no-console": 2 // Remember, this means error! 14 | } 15 | } -------------------------------------------------------------------------------- /.github/workflows/node.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | name: Server Node.js ${{ matrix.node-version }} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [17.x] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm install 23 | - run: npm run build --if-present 24 | - run: npm test 25 | - name: Building Client 26 | run: cd client/ 27 | - run: npm install 28 | - run: npm run lint --if-present 29 | - run: npm run build --if-present 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | .env 37 | server.js 38 | dist/ 39 | -------------------------------------------------------------------------------- /.history/package_20230114161045.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aliascheck", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "tsc --project './tsconfig.json' --watch & ts-node server/server.ts", 7 | "build": "tsc --project './tsconfig.build.json'", 8 | "start": "ts-node server/server.ts", 9 | "lint": "eslint . --ext .ts", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.27.2", 14 | "dotenv": "^16.0.1", 15 | "express": "^4.18.1", 16 | "http-status-codes": "^2.2.0", 17 | "i": "^0.3.7", 18 | "twitter-api-v2": "^1.12.5", 19 | "winston": "^3.8.1" 20 | }, 21 | "devDependencies": { 22 | "@types/express": "^4.17.13", 23 | "@types/jest": "^28.1.7", 24 | "@types/node": "^18.0.0", 25 | "@types/passport": "^1.0.11", 26 | "@types/react": "18.0.14", 27 | "@types/react-dom": "18.0.5", 28 | "@types/supertest": "^2.0.12", 29 | "@typescript-eslint/eslint-plugin": "^5.33.1", 30 | "@typescript-eslint/parser": "^5.33.1", 31 | "autoprefixer": "^10.4.7", 32 | "eslint": "^8.22.0", 33 | "eslint-config-next": "12.1.6", 34 | "jest": "^28.1.3", 35 | "nodemon": "^2.0.19", 36 | "supertest": "^6.2.4", 37 | "ts-jest": "^28.0.8", 38 | "ts-node": "^10.9.1", 39 | "typescript": "^4.7.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.history/package_20230114163606.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch", 11 | "test:ci": "jest --ci" 12 | }, 13 | "proxy": "http://server:5000/", 14 | "dependencies": { 15 | "@emotion/cache": "^11.10.2", 16 | "@emotion/react": "^11.9.3", 17 | "@emotion/server": "^11.10.0", 18 | "@emotion/styled": "^11.9.3", 19 | "@fortawesome/fontawesome-svg-core": "^6.2.0", 20 | "@fortawesome/free-brands-svg-icons": "^6.2.0", 21 | "@fortawesome/free-solid-svg-icons": "^6.2.0", 22 | "@fortawesome/react-fontawesome": "^0.2.0", 23 | "@mui/icons-material": "^5.8.4", 24 | "@mui/material": "^5.8.7", 25 | "@mui/styles": "^5.10.3", 26 | "install": "^0.13.0", 27 | "next": "12.1.6", 28 | "next-themes": "^0.2.1", 29 | "npm": "^8.19.2", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-icons": "^4.4.0", 33 | "react-toggle-dark-mode": "^1.1.0" 34 | }, 35 | "devDependencies": { 36 | "@testing-library/jest-dom": "^5.16.5", 37 | "@testing-library/react": "^13.4.0", 38 | "@testing-library/user-event": "^14.4.3", 39 | "@types/node": "18.0.0", 40 | "@types/react": "18.0.14", 41 | "@types/react-dom": "18.0.5", 42 | "eslint": "8.18.0", 43 | "eslint-config-next": "12.1.6", 44 | "jest": "^29.3.1", 45 | "jest-environment-jsdom": "^29.3.1", 46 | "typescript": "4.7.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.17.0-alpine 2 | WORKDIR /usr 3 | COPY . . 4 | RUN npm install 5 | EXPOSE ${PORT} 6 | CMD ["npm","run","dev"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **ALIAS CHECK** 2 | 3 | The Alias Check helps confirm the availability of a username on different social networks. While it is intended as a learning project by the [Tech Interviews Nigeria](https://www.meetup.com/technicalinterviews/) that would introduce participants to different technologies in fullstack development, ranging from frontend technologies like [NextJS](https://nextjs.org/) to [NodeJS](https://nodejs.org/en/about/) and [SQLite](https://www.sqlite.org/index.html) or [Postgres](https://www.postgresql.org/) on the backend in addition to [Docker](https://www.docker.com/) for development, its utility is quite apparent. For example, small businesses can see the application in the selection of an available and catchy name among major social media platform. 4 | 5 | This is project is structured so that the developer can choose to set it up with or without Docker. 6 | 7 | 8 | 9 | # **PROJECT SETUP** 10 | 11 | ## **With Docker Compose** 12 | 13 | To start the project using Docker, even from scratch, run the following command: 14 | 15 | ```bash 16 | cp .env.sample .env 17 | docker-compose up 18 | ``` 19 | 20 | Then go to [http://localhost:3000/](http://localhost:3000/) to access the **frontend** or [http://localhost:8080/](http://localhost:8080/) to access the **backend** 21 | 22 | ### **Backend-only Development** 23 | 24 | For **backend developers** that might only need to run the `server` and `database`, run the following command: 25 | 26 | ```bash 27 | docker-compose up server 28 | ``` 29 | 30 | ### **Frontend-only Development** 31 | 32 | For **frontend developers** who might not need access to the backend for the features they want to add, stop other services besides `client` after starting up: 33 | 34 | ```bash 35 | docker-compose up && \ 36 | docker stop $(docker-compose ps --services | grep -v client) 37 | ``` 38 | 39 | 40 | 41 | Checkout docker-compose [documentation](https://docs.docker.com/compose/reference/) on some of the commandline features available. 42 | 43 | ## **Without Docker Compose** 44 | 45 | Though this project was built for easy startup with Docker, it can also be run without Docker. You would need to run the commands on two terminals, one for the _frontend_ and the other for the _backend_: 46 | 47 | ## **Frontend** 48 | 49 | On one terminal, go to the frontend folder named `client`, install dependencies and run the development server: 50 | 51 | ```bash 52 | cd client 53 | npm install 54 | npm run dev 55 | ``` 56 | 57 | You should now be able to see the frontend at [http://localhost:3000/](http://localhost:3000/) 58 | 59 | ## **Backend** 60 | 61 | The backend requires authorization keys from the social media platforms that we support and will increase as time goes by: 62 | 63 | - For twitter :Register for a twitter developer account at [twitter developer account](https://developer.twitter.com/) then create api keys. Of importance is the `Bearer Token` to which on our .env file populate the key `TWITTER_BEARER_TOKEN` 64 | 65 | To start the backend application, make sure you have typescript installed on your system. Checkout [https://www.typescriptlang.org/download](https://www.typescriptlang.org/download). Once installed, run; 66 | 67 | ```bash 68 | cp .env.sample .env 69 | npm install 70 | npm run dev 71 | ``` 72 | 73 | Access the application at the address [http://localhost:8080/health](http://localhost:8080/health) 74 | 75 | ### Testing 76 | 77 | From the root directory; 78 | 79 | ```sh 80 | npm run test 81 | ``` 82 | 83 | ### UI / UX Resources 84 | 85 | You may find some useful resources for improving the UI / UX of the app [here](https://www.figma.com/file/9KXFSDfZzIr9kaCEqpBsc5/Aliascheck%3A-Open-source?node-id=157%3A82). 86 | 87 | ### Architecture 88 | 89 | This is roughly the architecture for the backend 90 |  91 | -------------------------------------------------------------------------------- /architecture/backend.dio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /architecture/backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanaryStack/aliascheck/60623b76aa1a41fdb82df0aff3e544da1ed9377c/architecture/backend.png -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | *.md 4 | node_modules 5 | build -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /client/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to AliasCheck - Frontend (Client) 2 | 3 | ❤ Thank you for your interest to contribute 🎉🎉🎉 4 | 5 | Below are set of guidelines for contributing to AliasCheck and its packages. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ### TABLE OF CONTENT 8 | 9 | - Code Guidelines 10 | - How to Contribute 11 | - How to Propose A New Issue 12 | - Pull Request Guides 13 | - Fork a Repository - Step by Step 14 | 15 | --- 16 | 17 | ## Code Guidelines 18 | 19 | - Write comprehensive and robust tests that cover the changes you've made in your work. 20 | 21 | * Follow the appropriate code style standards for the language and framework you're using. 22 | * Write readable code – keep functions small and modular and name variables descriptively. 23 | * Document your code thoroughly. 24 | * Make sure all the existing tests pass. 25 | * User-facing code should support the following browsers: 26 | Chrome (Webkit-Blink / 22+), 27 | Firefox (Gecko / 28+), 28 | Edge (Chromium based / 12+), 29 | Opera (Chromium-Blink / 12.1+), 30 | Safari (Apple’s Webkit / 7+), 31 | IE 11 (Trident) 32 | 33 | ## How to Contribute 34 | 35 | Once you've found an issue you'd like to work on, please follow these steps to make your contribution: 36 | 37 | 1. Comment on it and say you're working on that issue. This is to avoid conflicts with others also working on the issue. You will be required to be present for the community standups so as to ensure that the entire team is kept in view of your progress. 38 | > Please note that if you indicate interest and do not show any form of activeness by joining the community standups, and contributing in the available slack channels for 2 weeks, we would assume that you no longer have interest contributing to the selected issue 39 | 2. Submit your pull request and wait for your code review. Be sure to read and follow our pull request guidelines! 40 | 3. Wait for code review and address any issues raised as soon as you can. 41 | 42 | > We encourage people to collaborate as much as possible. When you reviewing each other pull requests be kind and constructive. We especially appreciate contributors reviewing each others pull requests. 43 | 44 | ## How to Propose A New Issue 45 | 46 | If you want to work on something that there is no GitHub issue for, kindly follow these steps: 47 | 48 | - Create a new GitHub issue associated with the relevant repository and propose your change there. Be sure to include implementation details and the rationale for the proposed change. 49 | - We are very reluctant to accept random pull requests without a related issue created first. This would mean that all pull request must be associated with an issue. 50 | - Wait for a project maintainer to evaluate your issue and decide whether it's something that we will accept a pull request for. 51 | - Once the project maintainer has approved the issue you may start work on code as described in the How to contribute section above. 52 | 53 | ## Pull Request Guides 54 | 55 | This guide is for both first time contributors and previous contributors. 56 | 57 | - You are required to create a separate branch for each issue that you're working on. Do not make changes to the default branch of your fork. See how to Fork A Repository 58 | - Push your code once you are done solving the issue or at a stage where you require review of your progress. 59 | - Make a pull request when you can. 60 | > If you are still working on the issue but have opened a draft pull request, please mark the title with a [WIP] 61 | 62 | > Pull Request with a [WIP] title wil not be reviewed. 63 | 64 | > Note that all pull requests must be linked to an issue else it would not be giving attention.. see How to Propose A New Issue section above. 65 | 66 | - Describe your pull request in detail. Too much detail is always better than too little 67 | 68 | > If you are not the author of an issue please tag the actual issue author. using @[author] ( This brings the reporter of the issue into the conversation ) 69 | 70 | - Check the Preview tab to make sure the Markdown is correctly rendered and that all tags and references are linked. If not, go back and edit the Markdown. 71 | 72 | - Once your PR is ready, remove the [WIP] from the title and/or change it from a draft PR to a regular PR. 73 | - If a specific reviewer is not assigned automatically, please request a review from the project maintainer [@simplytunde](https://github.com/simplytunde) and any other interested parties manually 74 | 75 | Backend - [@ChubaOraka](https://github.com/ChubaOraka), [@KiptoonKipkurui](https://github.com/KiptoonKipkurui) 76 | 77 | Frontend - [@Maxwell-ihiaso](https://github.com/Maxwell-ihiaso), [@RWambui](https://github.com/RWambui) 78 | 79 | --- 80 | 81 | - If your PR gets a 'Changes requested' review, you will need to address the feedback and update your PR by pushing to the same branch. You don't need to close the PR and open a new one. 82 | - Be sure to re-request review once you have made changes after a code review. 83 | - Asking for a re-review makes it clear that you addressed the changes that were requested and that it's waiting on the maintainers instead of the other way round. 84 | 85 | ## Fork a Repository 86 | 87 | **Guide to get started with forking a repository** 88 | 89 | 1. On the [Github page for this repository](https://github.com/KanaryStack/aliascheck), click on "Fork" 90 | 91 | 92 | 93 | 94 | 95 | 2. Clone your forked repository to your computer: 96 | 97 | 98 | 99 | ✅run this command inside your terminal: 100 | 101 | ``` 102 | git clone https://github.com/KanaryStack/aliascheck.git 103 | ``` 104 | 105 | see more about [forking]() and [cloning a repo]() 106 | 107 | 3. Shift to project directory: 108 | 109 | ``` 110 | cd aliascheck/client 111 | ``` 112 | 113 | 4. Before making any changes, keep your forked repository in sync to avoid merge conflicts: 114 | 115 | ``` 116 | git remote add upstream https://github.com/KanaryStack/aliascheck.git 117 | git pull upstream main 118 | ``` 119 | 120 | If you run into a merge conflict, you have to resolve the conflict. You can find online guides [here](https://opensource.com/article/20/4/git-merge-conflict) 121 | 122 | 5. after adding the upstream and checking that all files are up to date, you can now create a new branch before editing any files. You can achieve that in two ways: 123 | 124 | ``` 125 | git checkout -b 126 | ``` 127 | 128 | ``` 129 | git branch 130 | git switch 131 | ``` 132 | 133 | 6. On your directory, open text editor and add your changes/components 134 | 135 | 7. Add the changes with git add, git commit ([write a good commit message](https://cbea.ms/git-commit/)); 136 | 137 | ``` 138 | git add src/hero/index.tsx 139 | git commit -m "updated hero component" 140 | ``` 141 | 142 | 8. Push your changes to your repository: 143 | 144 | ``` 145 | git push origin 146 | ``` 147 | 148 | 9. Go to the Github page of your fork, and make a pull request: 149 | 150 | If you want to see more on the [pull request pages](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 151 | 152 | 10. Wait for one of the maintainers to merge your pull request. If you experience any conflict, you will be alerted. 153 | 154 | 11. Don't be shy and enjoy creating things together! 155 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-slim 2 | 3 | WORKDIR /usr/src/app/client 4 | 5 | COPY package*.json ./ 6 | 7 | RUN mkdir .next && npm install --legacy-peer-deps 8 | 9 | RUN chown -Rh node:node .next node_modules 10 | 11 | USER node 12 | 13 | EXPOSE 3000 14 | 15 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ### Please see the [CONTIBUTING.md](./CONTRIBUTING.md) if you would like to contribute to this Repo 2 | 3 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | npm run dev 11 | # or 12 | yarn dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /client/jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require("next/jest"); 2 | 3 | const createJestConfig = nextJest({ 4 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 5 | dir: "./", 6 | }); 7 | 8 | // Add any custom config to be passed to Jest 9 | const customJestConfig = { 10 | setupFilesAfterEnv: ["/jest.setup.js"], 11 | moduleNameMapper: { 12 | // Handle module aliases (this will be automatically configured for you soon) 13 | "^@/components/(.*)$": "/components/$1", 14 | 15 | "^@/pages/(.*)$": "/pages/$1", 16 | }, 17 | testEnvironment: "jest-environment-jsdom", 18 | }; 19 | 20 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 21 | module.exports = createJestConfig(customJestConfig); 22 | -------------------------------------------------------------------------------- /client/jest.setup.js: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | import "@testing-library/jest-dom/extend-expect"; 7 | -------------------------------------------------------------------------------- /client/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /client/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | webpackDevMiddleware: config => { 5 | config.watchOptions = { 6 | poll: 1000, 7 | aggregateTimeout: 300, 8 | } 9 | 10 | return config 11 | }, 12 | } 13 | 14 | module.exports = nextConfig 15 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch", 11 | "test:ci": "jest --ci" 12 | }, 13 | "proxy": "http://server:5000/", 14 | "dependencies": { 15 | "@emotion/cache": "^11.10.2", 16 | "@emotion/react": "^11.9.3", 17 | "@emotion/server": "^11.10.0", 18 | "@emotion/styled": "^11.9.3", 19 | "@fortawesome/fontawesome-svg-core": "^6.2.0", 20 | "@fortawesome/free-brands-svg-icons": "^6.2.0", 21 | "@fortawesome/free-solid-svg-icons": "^6.2.0", 22 | "@fortawesome/react-fontawesome": "^0.2.0", 23 | "@mui/icons-material": "^5.8.4", 24 | "@mui/material": "^5.8.7", 25 | "@mui/styles": "^5.10.3", 26 | "install": "^0.13.0", 27 | "next": "12.1.6", 28 | "next-themes": "^0.2.1", 29 | "npm": "^8.19.2", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0", 32 | "react-icons": "^4.4.0", 33 | "react-toggle-dark-mode": "^1.1.0" 34 | }, 35 | "devDependencies": { 36 | "@testing-library/jest-dom": "^5.16.5", 37 | "@testing-library/react": "^13.4.0", 38 | "@testing-library/user-event": "^14.4.3", 39 | "@types/node": "18.0.0", 40 | "@types/react": "18.0.14", 41 | "@types/react-dom": "18.0.5", 42 | "eslint": "8.18.0", 43 | "eslint-config-next": "12.1.6", 44 | "jest": "^29.3.1", 45 | "jest-environment-jsdom": "^29.3.1", 46 | "typescript": "4.7.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { AppProps } from "next/app"; 3 | import PageProvider from "../src/utils/PageProvider"; 4 | import CssBaseline from "@mui/material/CssBaseline"; 5 | import { CacheProvider, EmotionCache, css } from "@emotion/react"; 6 | import createEmotionCache from "../src/utils/createEmotionCache"; 7 | import { ThemeProvider } from "next-themes"; 8 | import { GlobalStyles } from "@mui/material"; 9 | // import Layout from "../src/utils/Layout"; 10 | 11 | // Client-side cache, shared for the whole session of the user in the browser. 12 | const clientSideEmotionCache = createEmotionCache(); 13 | 14 | interface MyAppProps extends AppProps { 15 | emotionCache?: EmotionCache; 16 | } 17 | 18 | export default function MyApp(props: MyAppProps) { 19 | const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; 20 | return ( 21 | // 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 45 | 46 | 47 | 48 | 49 | // 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /client/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Document, { Html, Head, Main, NextScript } from "next/document"; 3 | // import createEmotionServer from "@emotion/server/create-instance"; 4 | // import createEmotionCache from "../src/utils/createEmotionCache"; 5 | import { ServerStyleSheets } from "@mui/styles"; 6 | import { theme } from "../src/utils/theme"; 7 | 8 | export default class MyDocument extends Document { 9 | render() { 10 | return ( 11 | 12 | 13 | {/* PWA primary color */} 14 | 15 | 16 | 17 | 18 | 22 | {(this.props as any).emotionStyleTags} 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | 33 | // `getInitialProps` belongs to `_document` (instead of `_app`), 34 | // it's compatible with static-site generation (SSG). 35 | MyDocument.getInitialProps = async (ctx) => { 36 | // Resolution order 37 | // 38 | // On the server: 39 | // 1. app.getInitialProps 40 | // 2. page.getInitialProps 41 | // 3. document.getInitialProps 42 | // 4. app.render 43 | // 5. page.render 44 | // 6. document.render 45 | // 46 | // On the server with error: 47 | // 1. document.getInitialProps 48 | // 2. app.render 49 | // 3. page.render 50 | // 4. document.render 51 | // 52 | // On the client 53 | // 1. app.getInitialProps 54 | // 2. page.getInitialProps 55 | // 3. app.render 56 | // 4. page.render 57 | 58 | const sheets = new ServerStyleSheets(); 59 | const originalRenderPage = ctx.renderPage; 60 | 61 | // You can consider sharing the same Emotion cache between all the SSR requests to speed up performance. 62 | // However, be aware that it can have global side effects. 63 | // const cache = createEmotionCache(); 64 | // const { extractCriticalToChunks } = createEmotionServer(cache); 65 | 66 | // ctx.renderPage = () => 67 | // originalRenderPage({ 68 | // enhanceApp: (App: any) => 69 | // function EnhanceApp(props) { 70 | // return ; 71 | // }, 72 | // }); 73 | 74 | // const initialProps = await Document.getInitialProps(ctx); 75 | // // This is important. It prevents Emotion to render invalid HTML. 76 | // // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 77 | // const emotionStyles = extractCriticalToChunks(initialProps.html); 78 | // const emotionStyleTags = emotionStyles.styles.map((style) => ( 79 | // 85 | // )); 86 | 87 | // return { 88 | // ...initialProps, 89 | // emotionStyleTags, 90 | // }; 91 | // }; 92 | 93 | ctx.renderPage = () => 94 | originalRenderPage({ 95 | enhanceApp: (App) => (props) => sheets.collect(), 96 | }); 97 | 98 | const initialProps = await Document.getInitialProps(ctx); 99 | 100 | return { 101 | ...initialProps, 102 | // Styles fragment is rendered after the app and page rendering finish. 103 | styles: [ 104 | ...React.Children.toArray(initialProps.styles), 105 | sheets.getStyleElement(), 106 | ], 107 | }; 108 | }; 109 | -------------------------------------------------------------------------------- /client/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /client/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { NextPage } from "next"; 3 | import Head from "next/head"; 4 | import Search from "../src/components/Search"; 5 | import Hint from "../src/components/Hint"; 6 | import Navbar from "../src/components/Navbar"; 7 | import Footer from "../src/components/Footer"; 8 | import SearchResult from "../src/components/SearchResult"; 9 | import { UserInfoProvider } from "../src/utils/userInfoContext"; 10 | // import { useTheme } from "next-themes"; 11 | // import { DarkModeSwitch } from "react-toggle-dark-mode"; 12 | import Intro from "../src/components/intro/Intro"; 13 | 14 | const Home: NextPage = () => { 15 | // const { theme, resolvedTheme, setTheme } = useTheme(); 16 | return ( 17 | <> 18 | 19 | Alias Check 20 | 24 | 25 | {/**=============================================== 26 | * 27 | * UserInfoProvider uses the contextAPI to provide access to 28 | * state to all components 29 | * 30 | * All child elements needs to be wrapped with the 31 | * [UseinfoProvider] 32 | * 33 | * =====================================*/} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | > 43 | ); 44 | }; 45 | 46 | export default Home; 47 | -------------------------------------------------------------------------------- /client/public/assets/images/Tiktok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/public/assets/images/facebookimg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/public/assets/images/snapchatimg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/public/assets/people_alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanaryStack/aliascheck/60623b76aa1a41fdb82df0aff3e544da1ed9377c/client/public/assets/people_alias.png -------------------------------------------------------------------------------- /client/public/assets/people_alias2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/public/assets/people_alias3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanaryStack/aliascheck/60623b76aa1a41fdb82df0aff3e544da1ed9377c/client/public/assets/people_alias3.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanaryStack/aliascheck/60623b76aa1a41fdb82df0aff3e544da1ed9377c/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/intro/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/public/intro/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/public/intro/yellow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/components/ComingSoon/ComingSoon.tsx: -------------------------------------------------------------------------------- 1 | import { Typography, Box, useMediaQuery } from "@mui/material"; 2 | import useStyles from "./styles"; 3 | import Image from "next/image"; 4 | 5 | const Navbar: React.FC = () => { 6 | const classes = useStyles(); 7 | const matches = useMediaQuery("(min-width:600px)"); 8 | 9 | return ( 10 | <> 11 | 12 | {matches && ( 13 | 14 | 15 | 16 | )} 17 | 18 | 19 | Aliascheck 20 | 21 | 22 | 23 | Finding unique usernames{" "} 24 | just got easier 25 | {!matches && with} 26 | {!matches && Aliascheck} 27 | 28 | 29 | 35 | 36 | 37 | 38 | We help you find unique usernames early so that you can claim them. 39 | 40 | We understand the need of wanting to stand out on social media with 41 | usernames and how frustraing finding a unique one can be 42 | 43 | Coming soon 44 | 45 | {matches && ( 46 | 47 | 53 | 54 | )} 55 | 56 | > 57 | ); 58 | }; 59 | 60 | export default Navbar; 61 | -------------------------------------------------------------------------------- /client/src/components/ComingSoon/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | boxSizing: "border-box", 7 | position: "relative", 8 | transition: "all 0.2s linear", 9 | }, 10 | aligned: { 11 | maxWidth: "1200px", 12 | marginLeft: "auto", 13 | marginRight: "auto", 14 | }, 15 | title: { 16 | fontWeight: "normal", 17 | fontSize: "2rem", 18 | display: "flex", 19 | justifyContent: "center", 20 | alignItems: "center", 21 | padding: "0.5rem", 22 | backgroundColor: "#09334F", 23 | color: "#fff", 24 | 25 | [theme.breakpoints.up('md')]: { 26 | display: "inline-block", 27 | backgroundColor: "inherit", 28 | color: "#000", 29 | fontWeight: "bold", 30 | margin: "3rem 1.2rem 0", 31 | }, 32 | 33 | [theme.breakpoints.up('lg')]: { 34 | marginLeft: "3rem", 35 | }, 36 | }, 37 | headTitle: { 38 | margin: "1.5rem 1rem", 39 | padding: "1.8rem 0.5rem", 40 | borderRadius: "0.6rem", 41 | backgroundColor: "#EA9C0C", 42 | diplay: "flex", 43 | flexDirection: "column", 44 | justifyContent: "center", 45 | alignItems: "center", 46 | overflow: "hidden", 47 | height: "300px", 48 | 49 | [theme.breakpoints.up('xs')]: { 50 | padding: "2rem 1.1rem", 51 | margin: "2.5rem 1.5rem", 52 | height: "350px", 53 | }, 54 | 55 | [theme.breakpoints.up("sm")]: { 56 | content: 'Screen size = sm', 57 | height: "420px", 58 | }, 59 | [theme.breakpoints.up('md')]: { 60 | backgroundColor: "#fff", 61 | height: "auto", 62 | position: "relative", 63 | overflow: "visible", 64 | margin: "0 2rem", 65 | 66 | paddingLeft: "0", 67 | }, 68 | 69 | [theme.breakpoints.up('lg')]: { 70 | margin: "0 3rem", 71 | }, 72 | }, 73 | subtitle: { 74 | fontWeight: "normal", 75 | fontSize: "1.4rem", 76 | textAlign: "center", 77 | lineHeight: "1.4", 78 | 79 | [theme.breakpoints.up('xs')]: { 80 | fontSize: "1.6rem", 81 | }, 82 | 83 | [theme.breakpoints.up("sm")]: { 84 | content: 'Screen size = sm', 85 | fontSize: "1.9rem", 86 | }, 87 | 88 | [theme.breakpoints.up('md')]: { 89 | fontSize: "1.8rem", 90 | width: "60%", 91 | textAlign: "left", 92 | }, 93 | 94 | [theme.breakpoints.up("lg")]: { 95 | fontSize: "2.5rem", 96 | }, 97 | 98 | }, 99 | span: { 100 | padding: "0", 101 | margin: "0", 102 | backgroundColor: "#EA9C0C", 103 | 104 | [theme.breakpoints.up('md')]: { 105 | backgroundColor: "#EA9C0C", 106 | display: "inline-block", 107 | margin: "1rem 0 0.25rem", 108 | whiteSpace: "nowrap", 109 | textAlign: "left", 110 | minWidth: "max-content", 111 | fontWeight: "bold", 112 | padding: "0.2rem 0.5rem", 113 | }, 114 | }, 115 | image: { 116 | transform: "scale(0.45) translateY(-17rem)", 117 | display: "flex", 118 | justifyContent: "center", 119 | 120 | [theme.breakpoints.up('xs')]: { 121 | transform: "scale(0.45) translateY(-17rem)", 122 | }, 123 | 124 | [theme.breakpoints.up("sm")]: { 125 | content: 'Screen size = sm', 126 | transform: "scale(0.45) translateY(-22rem)", 127 | }, 128 | 129 | [theme.breakpoints.up('md')]: { 130 | transform: "scale(1.1) translateY(0)", 131 | position: "absolute", 132 | top: "-2rem", 133 | left: "65%", 134 | }, 135 | }, 136 | description: { 137 | fontSize: "1rem", 138 | margin: "0rem 1rem", 139 | textAlign: "center", 140 | 141 | [theme.breakpoints.up('xs')]: { 142 | fontSize: "1.2rem", 143 | }, 144 | 145 | [theme.breakpoints.up("sm")]: { 146 | content: 'Screen size = sm', 147 | margin: "0 1.5rem", 148 | fontSize: "1.4rem", 149 | }, 150 | 151 | [theme.breakpoints.up('md')]: { 152 | textAlign: "left", 153 | width: "55%", 154 | fontSize: "1.3rem", 155 | margin: "0 2rem", 156 | }, 157 | 158 | [theme.breakpoints.up('lg')]: { 159 | margin: "0 3rem", 160 | }, 161 | }, 162 | comingsoon: { 163 | color: "#031521", 164 | fontWeight: "bold", 165 | fontSize: "1.5rem", 166 | textAlign: "center", 167 | margin: "2rem auto 3rem", 168 | 169 | [theme.breakpoints.up("sm")]: { 170 | content: 'Screen size = sm', 171 | margin: "2.5rem auto 3.5rem", 172 | }, 173 | 174 | [theme.breakpoints.up('md')]: { 175 | textAlign: "left", 176 | marginLeft: "2rem", 177 | marginBottom: "7rem", 178 | display: "inline-block", 179 | }, 180 | 181 | [theme.breakpoints.up('lg')]: { 182 | marginLeft: "3rem", 183 | }, 184 | }, 185 | top: { 186 | display: "flex", 187 | alignItems: "flex-start", 188 | position: "absolute", 189 | top: "0", 190 | left: "40%", 191 | }, 192 | bottom: { 193 | display: "flex", 194 | alignItems: "flex-end", 195 | position: "absolute", 196 | top: "calc(100% - 100px)", 197 | left: "20%", 198 | }, 199 | })); -------------------------------------------------------------------------------- /client/src/components/Footer/Newsletter/index.tsx: -------------------------------------------------------------------------------- 1 | import useStyles from "./styles"; 2 | import { Typography, Grid, Box, Input, TextField, Button } from "@mui/material"; 3 | 4 | const Newsletter = () => { 5 | const classes = useStyles(); 6 | const handleChange = () => {}; 7 | return ( 8 | 9 | 10 | 20 | 21 | 22 | { 28 | alert("Submitted"); 29 | }} 30 | > 31 | Submit 32 | 33 | 34 | 35 | ); 36 | }; 37 | export default Newsletter; 38 | -------------------------------------------------------------------------------- /client/src/components/Footer/Newsletter/styles.ts: -------------------------------------------------------------------------------- 1 | 2 | import { makeStyles } from "@mui/styles"; 3 | import { Theme } from "@mui/material"; 4 | 5 | export default makeStyles((theme: Theme) => ({ 6 | input: { 7 | width: "95%", 8 | background: "white", 9 | "::placeholder": { 10 | color: "#000", 11 | }, 12 | }, 13 | inputItems: { 14 | "& > input": { 15 | fontFamily: "Poppins", 16 | outline: "0", 17 | border: "0", 18 | background: "#fff", 19 | borderRadius: "0.375rem", 20 | textAlign: "center", 21 | marginTop: ".6rem", 22 | }, 23 | "& > input::placeholder": { 24 | fontSize: ".85rem", 25 | color: "black", 26 | }, 27 | [theme.breakpoints.down("xs")]: { 28 | "& > input": { 29 | padding: ".3rem .8rem", 30 | marginLeft: "1.5rem", 31 | }, 32 | }, 33 | [theme.breakpoints.up("xs")]: { 34 | display: "flex", 35 | flexDirection: "column", 36 | alignItems: "center", 37 | gap: 6, 38 | marginTop: 10, 39 | "& > input": { 40 | padding: ".3rem .6rem", 41 | }, 42 | }, 43 | [theme.breakpoints.up("sm")]: { 44 | display: "flex", 45 | flexDirection: "column", 46 | gap: 7, 47 | }, 48 | [theme.breakpoints.up("md")]: { 49 | display: "flex", 50 | flexDirection: "column", 51 | alignItems: "center", 52 | "& > input::placeholder": { 53 | fontSize: ".9rem", 54 | }, 55 | }, 56 | [theme.breakpoints.up("lg")]: { 57 | display: "flex", 58 | alignItems: "center", 59 | "& > input": { 60 | padding: ".3rem .1rem", 61 | }, 62 | "& > input::placeholder": { 63 | fontSize: ".9rem", 64 | }, 65 | }, 66 | [theme.breakpoints.up("xl")]: { 67 | padding: "0 2.5rem", 68 | "& > input": { 69 | padding: ".3rem 4rem", 70 | }, 71 | "& > input::placeholder": { 72 | fontSize: "1rem", 73 | }, 74 | }, 75 | }, 76 | submit: { 77 | cursor: "pointer", 78 | boxShadow: 'none', 79 | textTransform: 'none', 80 | fontSize: 16, 81 | padding: '.17rem 1rem', 82 | border: '.0031rem solid', 83 | color: "#F0FDF4", 84 | backgroundColor: "#09334F", 85 | margin: "1rem 0", 86 | "&:hover": { 87 | transform: "scale(0.9)", 88 | backgroundColor: '#09334F', 89 | borderColor: '#0062cc', 90 | boxShadow: 'none', 91 | }, 92 | '&:active': { 93 | boxShadow: 'none', 94 | backgroundColor: '#0062cc', 95 | borderColor: '#005cbf', 96 | }, 97 | '&:focus': { 98 | boxShadow: '0 0 0 0.2rem rgba(0,123,255,.5)', 99 | }, 100 | }, 101 | })); -------------------------------------------------------------------------------- /client/src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import useStyles from "./styles"; 2 | import { Typography, Divider, IconButton, Box } from "@mui/material"; 3 | import GitHubIcon from "@mui/icons-material/GitHub"; 4 | import TwitterIcon from "@mui/icons-material/Twitter"; 5 | import LinkedInIcon from "@mui/icons-material/LinkedIn"; 6 | import Newsletter from "./Newsletter"; 7 | import Link from "next/link"; 8 | 9 | const Footer: React.FC = () => { 10 | const classes = useStyles(); 11 | return ( 12 | <> 13 | 14 | 15 | 16 | Aliascheck 17 | 18 | We help you find unique usernames early so that you can claim 19 | them. 20 | 21 | 22 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Subscribe to our newsletter 42 | 43 | 44 | 45 | 46 | 47 | 48 | Menu 49 | {["Services", "Signup", "Privacy"].map((menu, index) => ( 50 | 51 | {menu} 52 | 53 | ))} 54 | 55 | 56 | Policies 57 | {["Terms", "Cookies", "FAQs"].map((policy, index) => ( 58 | 59 | {policy} 60 | 61 | ))} 62 | 63 | 64 | 65 | 66 | 67 | ©{new Date().getFullYear()} Aliascheck. All rights reserved. 68 | 69 | 70 | 71 | > 72 | ); 73 | }; 74 | export default Footer; 75 | -------------------------------------------------------------------------------- /client/src/components/Footer/styles.ts: -------------------------------------------------------------------------------- 1 | 2 | import { makeStyles } from "@mui/styles"; 3 | import { Theme } from "@mui/material"; 4 | 5 | export default makeStyles((theme: Theme) => ({ 6 | root: { 7 | marginTop: "3.75rem", 8 | flexGrow: 1, 9 | backgroundColor: "#FAB535", 10 | color: "#09334F", 11 | fontFamily: "Poppins", 12 | }, 13 | footer: { 14 | display: "flex", 15 | justifyContent: "space-around", 16 | px: 3, 17 | [theme.breakpoints.down("md")]: { 18 | display: "grid", 19 | }, 20 | [theme.breakpoints.up("xs")]: { 21 | margin: "0 1.5rem", 22 | }, 23 | [theme.breakpoints.down("xs")]: { 24 | display: "grid", 25 | gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", 26 | gridgap: "1rem", 27 | maxWidth: "100%", 28 | fontSize: "16px", 29 | lineHeight: 1.5, 30 | padding: "15px", 31 | margin: "0 1rem", 32 | }, 33 | }, 34 | first: { 35 | textAlign: "center", 36 | [theme.breakpoints.up("md")]: { 37 | width: "20%", 38 | }, 39 | }, 40 | second: { 41 | fontSize: "1.2rem", 42 | textAlign: "center", 43 | [theme.breakpoints.up("md")]: { 44 | width: "35%", 45 | }, 46 | [theme.breakpoints.down("md")]: { 47 | border: ".005px solid gray", 48 | borderWidth: ".002px", 49 | }, 50 | 51 | }, 52 | subscribeHeader: { 53 | [theme.breakpoints.down("md")]: { 54 | fontSize: "1.2rem", 55 | }, 56 | [theme.breakpoints.down("xs")]: { 57 | fontSize: "1rem", 58 | }, 59 | }, 60 | third: { 61 | fontSize: "1.2rem", 62 | textAlign: "left", 63 | [theme.breakpoints.up("md")]: { 64 | width: "10%", 65 | }, 66 | [theme.breakpoints.down("md")]: { 67 | fontSize: "1rem", 68 | textAlign: "center", 69 | }, 70 | }, 71 | fourth: { 72 | fontSize: "1.2rem", 73 | textAlign: "left", 74 | marginBottom: "16px", 75 | [theme.breakpoints.down("md")]: { 76 | fontSize: "1.094rem", 77 | }, 78 | [theme.breakpoints.up("md")]: { 79 | width: "10%", 80 | }, 81 | [theme.breakpoints.down("md")]: { 82 | textAlign: "center", 83 | fontSize: "1rem", 84 | }, 85 | }, 86 | list: { 87 | lineHeight: 1.6, 88 | fontFamily: "poppins", 89 | fontSize: "1.15rem", 90 | cursor: "pointer", 91 | }, 92 | footerText: { 93 | fontFamily: "Poppins", 94 | fontSize: "1.4rem", 95 | color: "#09334F", 96 | textAlign: "center", 97 | padding: "1.5rem", 98 | [theme.breakpoints.down("md")]: { 99 | fontSize: "1.094rem", 100 | }, 101 | }, 102 | })); -------------------------------------------------------------------------------- /client/src/components/Hint/index.tsx: -------------------------------------------------------------------------------- 1 | import useStyles from "./styles"; 2 | import { 3 | MdOutlineContentCopy, 4 | MdModeEditOutline, 5 | MdOutlineCheck, 6 | } from "react-icons/md"; 7 | 8 | const hints = [ 9 | { 10 | id: 1, 11 | icon: , 12 | description: "Enter username", 13 | }, 14 | { 15 | id: 2, 16 | icon: , 17 | description: "Search if username is available", 18 | }, 19 | { 20 | id: 3, 21 | icon: , 22 | description: "Copy available usernames", 23 | }, 24 | ]; 25 | 26 | const Hint: React.FC = () => { 27 | const classes = useStyles(); 28 | return ( 29 | 30 | How it works 31 | 32 | {hints.map((hint) => ( 33 | 34 | {hint.icon} 35 | {hint.description} 36 | 37 | ))} 38 | 39 | 40 | ); 41 | }; 42 | 43 | export default Hint; 44 | -------------------------------------------------------------------------------- /client/src/components/Hint/styles.ts: -------------------------------------------------------------------------------- 1 | import {Theme} from "@mui/material"; 2 | import {makeStyles} from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | flexDirection: "column", 8 | alignItems: "center", 9 | width: "100%", 10 | gap: "2rem", 11 | "& > h2": { 12 | // textTransform: "capitalize", 13 | padding: "0", 14 | marign: "0", 15 | marginTop: "4rem", 16 | fontWeight: 700, 17 | }, 18 | marginBottom: "2rem", 19 | }, 20 | hintGroup: { 21 | display: "flex", 22 | flexDirection: "column", 23 | gap: "2rem", 24 | 25 | [theme.breakpoints.up("md")]: { 26 | flexDirection: "row", 27 | alignItems: "flex-start", 28 | gap: "4rem", 29 | }, 30 | 31 | [theme.breakpoints.up("lg")]: { 32 | justifyContent: "space-between", 33 | width: "80%", 34 | }, 35 | }, 36 | 37 | hint: { 38 | display: "flex", 39 | justifyContent: "center", 40 | fontWeight: 700, 41 | flexDirection: "column", 42 | alignItems: "center", 43 | padding: "1.5rem 1rem", 44 | gap: "1.5rem", 45 | borderRadius: "0.2rem", 46 | boxShadow: `0 0 1px ${theme.palette.secondary.light}`, 47 | 48 | "& > div": { 49 | background: theme.palette.primary.main, 50 | width: "50px", 51 | aspectRatio: "1/1", 52 | display: "flex", 53 | justifyContent: "center", 54 | alignItems: "center", 55 | }, 56 | 57 | [theme.breakpoints.up("xs")]: { 58 | padding: "1.8rem 1.5rem", 59 | }, 60 | 61 | [theme.breakpoints.up("sm")]: { 62 | padding: "2rem", 63 | }, 64 | 65 | [theme.breakpoints.up("md")]: { 66 | boxShadow: `none`, 67 | padding: "0", 68 | flex: "1", 69 | 70 | "& > p": { 71 | textAlign: "center", 72 | }, 73 | }, 74 | }, 75 | })); 76 | -------------------------------------------------------------------------------- /client/src/components/Navbar/Drawer/Drawer.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import useStyles from "./styles"; 3 | import { 4 | Box, 5 | Drawer, 6 | IconButton, 7 | List, 8 | ListItem, 9 | ListItemIcon, 10 | ListItemText, 11 | Divider, 12 | Typography, 13 | } from "@mui/material"; 14 | import MenuIcon from "@mui/icons-material/Menu"; 15 | import Switcher from "../Switcher"; 16 | import HomeIcon from "@mui/icons-material/Home"; 17 | import StarIcon from "@mui/icons-material/Star"; 18 | import MiscellaneousServicesIcon from "@mui/icons-material/MiscellaneousServices"; 19 | import ExitToAppIcon from "@mui/icons-material/ExitToApp"; 20 | import { useTheme, ThemeProvider } from "@mui/material/styles"; 21 | 22 | const drawerWidth = 150; 23 | const DrawerComp = () => { 24 | const theme = useTheme(); 25 | const [drawer, setDrawer] = useState(false); 26 | 27 | return ( 28 | <> 29 | 30 | setDrawer(false)} 46 | > 47 | 48 | 49 | Dark mode 50 | 51 | 52 | 53 | 54 | 55 | {[ 56 | { text: "Home", icon: }, 57 | { text: "About Us", icon: }, 58 | { text: "Services", icon: }, 59 | { text: "Sign Up", icon: }, 60 | ].map((item) => ( 61 | 62 | 63 | {item.icon} 64 | 65 | 73 | 74 | ))} 75 | 76 | 77 | setDrawer(!drawer)} 83 | size={"small"} 84 | > 85 | 86 | 87 | 88 | > 89 | ); 90 | }; 91 | 92 | //https://dribbble.com/shots/19798757-Nulis-User-Menu-Detail 93 | 94 | export default DrawerComp; 95 | -------------------------------------------------------------------------------- /client/src/components/Navbar/Drawer/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | root: { 6 | // backgroundColor: theme.palette.background.default, 7 | }, 8 | header: { 9 | display: "flex", 10 | alignItems: "center", 11 | padding: theme.spacing(0, 1), 12 | // necessary for content to be below app bar 13 | ...theme.mixins.toolbar, 14 | justifyContent: "space-between", 15 | }, 16 | })); -------------------------------------------------------------------------------- /client/src/components/Navbar/Switcher/index.tsx: -------------------------------------------------------------------------------- 1 | import { DarkModeSwitch } from "react-toggle-dark-mode"; 2 | import { useTheme } from "next-themes"; 3 | import { Box } from "@mui/material"; 4 | 5 | const Switcher = () => { 6 | const { theme, resolvedTheme, setTheme } = useTheme(); 7 | return ( 8 | 9 | setTheme(resolvedTheme === "light" ? "dark" : "light")} 12 | size={20} 13 | moonColor="#031521" 14 | sunColor="#FAB535" 15 | /> 16 | 17 | ); 18 | }; 19 | 20 | export default Switcher; 21 | -------------------------------------------------------------------------------- /client/src/components/Navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import useStyles from "./styles"; 2 | import { useState } from "react"; 3 | import { 4 | AppBar, 5 | IconButton, 6 | Button, 7 | Typography, 8 | Box, 9 | Tab, 10 | Tabs, 11 | Toolbar, 12 | useTheme, 13 | useMediaQuery, 14 | } from "@mui/material"; 15 | import MenuIcon from "@mui/icons-material/Menu"; 16 | import Switcher from "./Switcher"; 17 | import DrawerComp from "./Drawer/Drawer"; 18 | 19 | const Navbar: React.FC = () => { 20 | const classes = useStyles(); 21 | const [value, setValue] = useState(); 22 | const theme = useTheme(); 23 | const isMatch = useMediaQuery(theme.breakpoints.down("md")); 24 | 25 | return ( 26 | <> 27 | 28 | 29 | Aliascheck 30 | 31 | 32 | {isMatch ? ( 33 | <> 34 | 35 | > 36 | ) : ( 37 | <> 38 | setValue(value)} 44 | > 45 | 46 | 47 | 48 | 49 | Sign up 50 | 51 | > 52 | )} 53 | 54 | > 55 | ); 56 | }; 57 | export default Navbar; 58 | -------------------------------------------------------------------------------- /client/src/components/Navbar/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | justifyContent: "center", 8 | alignItems: "center", 9 | fontSize: "1.2rem", 10 | paddingTop: "0.1rem", 11 | flexGrow: 1, 12 | }, 13 | title: { 14 | fontWeight: 700, 15 | fontSize: "2rem", 16 | justifySelf: "start", 17 | cursor: "pointer", 18 | [theme.breakpoints.down("md")]: { 19 | position: "absolute", 20 | top: "0", 21 | left: "0", 22 | transform: "translate(13%, 35%)", 23 | fontSize: "1.7rem", 24 | }, 25 | }, 26 | button: { 27 | width: "100px", 28 | padding: "0.2rem .6rem", 29 | fontWeight: 600, 30 | fontSize: "1rem", 31 | marginLeft: ".6rem", 32 | color: theme.palette.primary.contrastText, 33 | background: theme.palette.primary.main, 34 | border: 0, 35 | borderRadius: ".125rem", 36 | cursor: "pointer", 37 | "&:hover": { 38 | transform: "scale(0.9)", 39 | transition: "all 0.3s ease", 40 | }, 41 | }, 42 | })); -------------------------------------------------------------------------------- /client/src/components/Search/SearchBox/SearchBox.test.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@mui/styles"; 2 | import { render, screen, cleanup, fireEvent } from "@testing-library/react"; 3 | import React from "react"; 4 | import { theme } from "../../../utils/theme"; 5 | import SearchBox from "./index"; 6 | 7 | const Wrapper = ({ children }: { children: React.ReactNode }) => { 8 | return {children}; 9 | }; 10 | 11 | describe("SearchBox", () => { 12 | beforeEach(() => cleanup()); 13 | 14 | it("Load and display the search box", () => { 15 | render( 16 | 17 | 18 | 19 | ); 20 | 21 | const searchButton = screen.getByRole("button", { name: /search/i }); 22 | const usernameInput = screen.getByRole("textbox", { 23 | name: /Enter username/, 24 | }); 25 | 26 | expect(usernameInput).toBeInTheDocument(); 27 | 28 | expect(screen.getByTestId("hint")).toHaveTextContent( 29 | "By using our service you accept our Terms of Service and Privacy Policy." 30 | ); 31 | 32 | expect(searchButton).toBeInTheDocument(); 33 | 34 | expect(searchButton).toHaveTextContent(/search/i); 35 | 36 | expect(usernameInput).toHaveValue(""); 37 | 38 | expect(usernameInput).toBeRequired(); 39 | 40 | expect(fireEvent.click(searchButton)).toBe(true); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /client/src/components/Search/SearchBox/index.tsx: -------------------------------------------------------------------------------- 1 | import { TextField, Box, Stack, Typography, Button } from "@mui/material"; 2 | import { useTheme } from "next-themes"; 3 | import { ChangeEvent, useEffect, useState } from "react"; 4 | import { useUserInfo } from "../../../utils/userInfoContext"; 5 | import useStyles from "./styles"; 6 | 7 | const SearchBox: React.FC = () => { 8 | const [modeColor, setModeColor] = useState(""); 9 | const { resolvedTheme } = useTheme(); 10 | const classes = useStyles(); 11 | const { setUsername } = useUserInfo(); 12 | useEffect(() => { 13 | setModeColor(resolvedTheme === "light" ? "black" : "white"); 14 | }, [resolvedTheme]); 15 | return ( 16 | 17 | 18 | ) => 38 | setUsername(e.target.value) 39 | } 40 | variant="outlined" 41 | required 42 | aria-required="true" 43 | /> 44 | 45 | By using our service you accept our Terms of Service and Privacy 46 | Policy. 47 | 48 | 49 | 54 | search 55 | 56 | 57 | ); 58 | }; 59 | 60 | export default SearchBox; 61 | -------------------------------------------------------------------------------- /client/src/components/Search/SearchBox/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | flexDirection: "column", 8 | alignItems: "center", 9 | gap: "2rem", 10 | width: "90%", 11 | maxWidth: "1600px", 12 | 13 | [theme.breakpoints.up("lg")]: { 14 | flexDirection: "row", 15 | justifyContent: "center", 16 | alignItems: "center", 17 | gap: "1rem", 18 | }, 19 | }, 20 | 21 | inputGroup: { 22 | display: "flex", 23 | flexDirection: "column", 24 | alignItems: "center", 25 | width: "100%", 26 | gap: "0.5rem", 27 | 28 | [theme.breakpoints.up("md")]: { 29 | width: "90%", 30 | }, 31 | 32 | /**============== TABLET ================ */ 33 | [theme.breakpoints.up("lg")]: { 34 | width: "60%", 35 | alignItems: "flex-start", 36 | }, 37 | }, 38 | 39 | button: { 40 | padding: "0.6rem 1.2rem", 41 | transition: "all 0.3s ease", 42 | borderRadius: "3rem", 43 | 44 | "&:hover": { 45 | transform: "scale(0.9)", 46 | }, 47 | 48 | [theme.breakpoints.up("lg")]: { 49 | alignSelf: "flex-start", 50 | padding: "0.8rem 2rem", 51 | }, 52 | }, 53 | })); 54 | -------------------------------------------------------------------------------- /client/src/components/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@mui/material"; 2 | import SearchBox from "../Search/SearchBox"; 3 | import useStyles from "./styles"; 4 | 5 | const Search: React.FC = () => { 6 | const classes = useStyles(); 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Search; 15 | -------------------------------------------------------------------------------- /client/src/components/Search/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | flexDirection: "column", 8 | alignItems: "center", 9 | width: "100%", 10 | 11 | "& > h3": { 12 | textTransform: "capitalize", 13 | padding: "0", 14 | marign: "0", 15 | }, 16 | 17 | "& > span": { 18 | display: "inline-block", 19 | marginTop: "1rem", 20 | marginRight: "1rem", 21 | fontSize: "0.8rem", 22 | textDecoration: "underline", 23 | cursor: "pointer", 24 | alignSelf: "flex-end", 25 | }, 26 | 27 | "& > span:hover": { 28 | color: theme.palette.primary.main, 29 | }, 30 | "& > span:active": { 31 | color: theme.palette.primary.main, 32 | }, 33 | 34 | [theme.breakpoints.up("md")]: { 35 | "& > span": { 36 | marginRight: "5%", 37 | }, 38 | }, 39 | 40 | [theme.breakpoints.up("lg")]: { 41 | gap: "1rem", 42 | "& > span": { 43 | alignSelf: "auto", 44 | margin: "0", 45 | }, 46 | }, 47 | }, 48 | })); -------------------------------------------------------------------------------- /client/src/components/SearchResult/index.tsx: -------------------------------------------------------------------------------- 1 | import useStyles from "./styles"; 2 | import { useTheme } from "next-themes"; 3 | import { Card, Typography, Box, Paper, TextField, Button } from "@mui/material"; 4 | import CardMedia from "@mui/material/CardMedia"; 5 | import CardHeader from "@mui/material/CardHeader"; 6 | import Container from "@mui/material/Container"; 7 | import { styled } from "@mui/material/styles"; 8 | import MuiAccordion, { AccordionProps } from "@mui/material/Accordion"; 9 | import MuiAccordionSummary, { 10 | AccordionSummaryProps, 11 | } from "@mui/material/AccordionSummary"; 12 | import MuiAccordionDetails from "@mui/material/AccordionDetails"; 13 | import { useEffect, useState } from "react"; 14 | 15 | // Social Media dummy datas 16 | const socials = [ 17 | { 18 | id: 1, 19 | img: "/assets/images/facebookimg.svg", 20 | title: "Facebook", 21 | subheader: "Avilable", 22 | }, 23 | { 24 | id: 2, 25 | img: "/assets/images/twitterimg.svg", 26 | title: "Twitter", 27 | subheader: "Available", 28 | }, 29 | { 30 | id: 3, 31 | img: "/assets/images/instagramimg.svg", 32 | title: "Instagram", 33 | subheader: "Available", 34 | }, 35 | { 36 | id: 4, 37 | img: "/assets/images/snapchatimg.svg", 38 | title: "Snapchat", 39 | subheader: "Available", 40 | }, 41 | { 42 | id: 5, 43 | img: "/assets/images/Tiktok.svg", 44 | title: "TikTok", 45 | subheader: "Available", 46 | }, 47 | ]; 48 | 49 | /* 50 | * Extra paper contain for the shadow blurry background 51 | */ 52 | const Item = styled(Paper)(({ theme }) => ({ 53 | ...theme.typography.body2, 54 | color: theme.palette.text.secondary, 55 | height: 74, 56 | width: 176, 57 | borderRadius: 10, 58 | })); 59 | 60 | /** 61 | * Custom Accordion styles 62 | */ 63 | const Accordion = styled((props: AccordionProps) => ( 64 | 65 | ))(() => ({ 66 | border: `none`, 67 | background: "transparent", 68 | "&:not(:last-child)": { 69 | borderBottom: 0, 70 | }, 71 | "&:before": { 72 | display: "none", 73 | }, 74 | })); 75 | 76 | const AccordionSummary = styled((props: AccordionSummaryProps) => ( 77 | 78 | ))(() => ({ 79 | "& .MuiAccordionSummary-content": { 80 | justifyContent: "center", 81 | }, 82 | })); 83 | 84 | const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ 85 | padding: theme.spacing(2), 86 | borderTop: "1px solid rgba(0, 0, 0, .125)", 87 | })); 88 | 89 | /** 90 | * Displays search results and captures details 91 | * for generating suggested usernames 92 | * @params NULL 93 | * @returns React.FC 94 | */ 95 | const SearchResult: React.FC = () => { 96 | const [modeColor, setModeColor] = useState(""); 97 | const classes = useStyles(); 98 | const { resolvedTheme } = useTheme(); 99 | 100 | useEffect(() => { 101 | setModeColor(resolvedTheme === "light" ? "black" : "white"); 102 | }, [resolvedTheme]); 103 | 104 | return ( 105 | // This the main containr with a width of 100% 106 | 107 | 108 | 117 | Results 118 | 119 | 127 | {socials.map((social) => ( 128 | 129 | 130 | 138 | } 139 | title={ 140 | 141 | {social.title} 142 | 143 | } 144 | subheader={ 145 | 146 | {social.subheader} 147 | 148 | } 149 | /> 150 | 151 | 152 | ))} 153 | 154 | 155 | 156 | 163 | span": { 169 | fontWeight: "bold", 170 | }, 171 | "& > span:hover": { 172 | color: "#FAB535", 173 | }, 174 | }} 175 | > 176 | More personalized options?{" "} 177 | See suggestion 178 | 179 | 180 | 185 | 201 | 209 | 221 | 233 | 245 | 257 | 258 | 259 | 264 | search 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | ); 273 | }; 274 | 275 | export default SearchResult; 276 | -------------------------------------------------------------------------------- /client/src/components/SearchResult/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | justifyContent: "center", 8 | alignItems: "center", 9 | }, 10 | 11 | socialhandles: { 12 | paddingTop: 5, 13 | display: "flex", 14 | alignItems: "center", 15 | flexWrap: "wrap", 16 | [theme.breakpoints.only("xl")]: { 17 | paddingRight: 3, 18 | paddingLeft: 3, 19 | marginRight: 2, 20 | marginLeft: 2, 21 | }, 22 | }, 23 | 24 | span: {}, 25 | button: { 26 | padding: "0.6rem 1.2rem", 27 | transition: "all 0.3s ease", 28 | 29 | "&:hover": { 30 | transform: "scale(0.9)", 31 | }, 32 | 33 | [theme.breakpoints.up("lg")]: { 34 | alignSelf: "flex-start", 35 | padding: "0.8rem 2rem", 36 | }, 37 | }, 38 | })); 39 | -------------------------------------------------------------------------------- /client/src/components/intro/Intro.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, Typography } from "@mui/material"; 3 | import useStyles from "./styles"; 4 | import clsx from "clsx"; 5 | 6 | function Intro() { 7 | const classes = useStyles(); 8 | return ( 9 | 10 | 11 | 12 | Hi, I found a username 13 | 14 | @johnDoe_🤗😎😉 15 | 16 | 17 | 18 | Find a unique username for 19 | social platforms 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | export default Intro; 27 | -------------------------------------------------------------------------------- /client/src/components/intro/styles.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@mui/material"; 2 | import { makeStyles } from "@mui/styles"; 3 | 4 | export default makeStyles((theme: Theme) => ({ 5 | container: { 6 | display: "flex", 7 | justifyContent: "center", 8 | alignItems: "center", 9 | margin: "7rem 0", 10 | marginBottom: "3rem", 11 | 12 | [theme.breakpoints.down("lg")]: {}, 13 | // [theme.breakpoints.down("md")]: { 14 | // borderBottom: "none", 15 | // }, 16 | // [theme.breakpoints.down("sm")]: { 17 | // }, 18 | // [theme.breakpoints.down("xs")]: { 19 | // }, 20 | }, 21 | heroContent: { 22 | maxWidth: "600px", 23 | }, 24 | 25 | typing_effect: { 26 | width: "100%", 27 | display: "flex", 28 | flexDirection: "column", 29 | paddingBottom: "1rem", 30 | 31 | "& h2:nth-child(1)": { 32 | color: "#355c7d", 33 | fontFamily: "'Fira Code', monospace", 34 | fontWeight: "800", 35 | fontSize: "20px", 36 | whiteSpace: "nowrap", 37 | overflow: "hidden", 38 | width: "max-content", 39 | animation: "type 7s steps(40,end) forwards", 40 | }, 41 | 42 | "& h2:nth-child(2)": { 43 | fontFamily: "'Work Sans', sans-serif", 44 | margin: "0 auto auto 5%", 45 | background: "linear-gradient(to right, #f8b195, #f67280, #c06c84)", 46 | fontWeight: "800", 47 | fontSize: "1.5rem", 48 | lineHeigh: "1.2", 49 | width: "0", 50 | whiteSpace: "nowrap", 51 | WebkitBackgroundClip: "text", 52 | WebkitTextFillColor: "transparent", 53 | overflow: "hidden", 54 | animation: "type 7s steps(40,end) 7s infinite", 55 | 56 | "& > span": { 57 | background: "#FAB535", 58 | WebkitBackgroundClip: "text", 59 | WebkitTextFillColor: "#FAB535", 60 | }, 61 | }, 62 | }, 63 | 64 | "@global @keyframes type": { 65 | "0%": { 66 | width: "0", 67 | }, 68 | "1%, 99%": { 69 | borderRight: "1px solid #FAB535", 70 | }, 71 | "100%": { 72 | borderRight: "none", 73 | width: "42%", 74 | }, 75 | }, 76 | 77 | hero: { 78 | textAlign: "center", 79 | fontWeight: 600, 80 | fontSize: "3rem", 81 | }, 82 | 83 | hero_fun: { 84 | color: "#FAB535", 85 | margin: "0 .125rem", 86 | marginLeft: ".5rem", 87 | }, 88 | })); 89 | -------------------------------------------------------------------------------- /client/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import { IconType } from "react-icons"; 2 | 3 | /**======= INTERFACE FOR REDUCER ACTION ======= */ 4 | export interface IUserInfoReducerAction { 5 | type: "SET_USERNAME"; 6 | payload: { [key: string]: string } | string; 7 | } 8 | 9 | /** == INTERFACE FOR HINT COMPONENT === */ 10 | export interface IHint { 11 | id: number; 12 | icon: IconType; 13 | description: string; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/utils/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Head from "next/head"; 3 | import Navbar from "../components/Navbar"; 4 | import Footer from "../components/Footer"; 5 | 6 | const Layout: React.FC = (children) => { 7 | return ( 8 | 9 | 10 | Alias Check 11 | 15 | 16 | 17 | 18 | 19 | {children} 20 | 23 | 24 | ); 25 | }; 26 | 27 | export default Layout; 28 | -------------------------------------------------------------------------------- /client/src/utils/Link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import clsx from "clsx"; 3 | import { useRouter } from "next/router"; 4 | import NextLink, { LinkProps as NextLinkProps } from "next/link"; 5 | import MuiLink, { LinkProps as MuiLinkProps } from "@mui/material/Link"; 6 | import { styled } from "@mui/material/styles"; 7 | 8 | // Add support for the sx prop for consistency with the other branches. 9 | const Anchor = styled("a")({}); 10 | 11 | interface NextLinkComposedProps 12 | extends Omit, "href">, 13 | Omit< 14 | NextLinkProps, 15 | "href" | "as" | "onClick" | "onMouseEnter" | "onTouchStart" 16 | > { 17 | to: NextLinkProps["href"]; 18 | linkAs?: NextLinkProps["as"]; 19 | } 20 | 21 | export const NextLinkComposed = React.forwardRef< 22 | HTMLAnchorElement, 23 | NextLinkComposedProps 24 | >(function NextLinkComposed(props, ref) { 25 | const { to, linkAs, replace, scroll, shallow, prefetch, locale, ...other } = 26 | props; 27 | 28 | return ( 29 | 39 | 40 | 41 | ); 42 | }); 43 | 44 | export type LinkProps = { 45 | activeClassName?: string; 46 | as?: NextLinkProps["as"]; 47 | href: NextLinkProps["href"]; 48 | linkAs?: NextLinkProps["as"]; // Useful when the as prop is shallow by styled(). 49 | noLinkStyle?: boolean; 50 | } & Omit & 51 | Omit; 52 | 53 | // A styled version of the Next.js Link component: 54 | // https://nextjs.org/docs/api-reference/next/link 55 | const Link = React.forwardRef(function Link( 56 | props, 57 | ref 58 | ) { 59 | const { 60 | activeClassName = "active", 61 | as, 62 | className: classNameProps, 63 | href, 64 | linkAs: linkAsProp, 65 | locale, 66 | noLinkStyle, 67 | prefetch, 68 | replace, 69 | role, // Link don't have roles. 70 | scroll, 71 | shallow, 72 | ...other 73 | } = props; 74 | 75 | const router = useRouter(); 76 | const pathname = typeof href === "string" ? href : href.pathname; 77 | const className = clsx(classNameProps, { 78 | [activeClassName]: router.pathname === pathname && activeClassName, 79 | }); 80 | 81 | const isExternal = 82 | typeof href === "string" && 83 | (href.indexOf("http") === 0 || href.indexOf("mailto:") === 0); 84 | 85 | if (isExternal) { 86 | if (noLinkStyle) { 87 | return ; 88 | } 89 | 90 | return ; 91 | } 92 | 93 | const linkAs = linkAsProp || as; 94 | const nextjsProps = { 95 | to: href, 96 | linkAs, 97 | replace, 98 | scroll, 99 | shallow, 100 | prefetch, 101 | locale, 102 | }; 103 | 104 | if (noLinkStyle) { 105 | return ( 106 | 112 | ); 113 | } 114 | 115 | return ( 116 | 123 | ); 124 | }); 125 | 126 | export default Link; 127 | -------------------------------------------------------------------------------- /client/src/utils/PageProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@mui/material"; 2 | import { useTheme } from "next-themes"; 3 | import { ReactNode, useEffect, useState } from "react"; 4 | import { theme, darkTheme, lightTheme } from "./theme"; 5 | 6 | interface PageProvidersProps { 7 | children: ReactNode; 8 | } 9 | 10 | const PageProvider = ({ children }: PageProvidersProps) => { 11 | const { resolvedTheme } = useTheme(); 12 | const [currentTheme, setCurrentTheme] = useState(lightTheme); 13 | useEffect(() => { 14 | resolvedTheme === "light" 15 | ? setCurrentTheme(lightTheme) 16 | : setCurrentTheme(darkTheme); 17 | }, [resolvedTheme]); 18 | return {children}; 19 | }; 20 | 21 | export default PageProvider; 22 | -------------------------------------------------------------------------------- /client/src/utils/createEmotionCache.ts: -------------------------------------------------------------------------------- 1 | import createCache from "@emotion/cache"; 2 | 3 | const isBrowser = typeof document !== "undefined"; 4 | 5 | // On the client side, Create a meta tag at the top of the and set it as insertionPoint. 6 | // This assures that MUI styles are loaded first. 7 | // It allows developers to easily override MUI styles with other styling solutions, like CSS modules. 8 | export default function createEmotionCache() { 9 | let insertionPoint; 10 | 11 | if (isBrowser) { 12 | const emotionInsertionPoint = document.querySelector( 13 | 'meta[name="emotion-insertion-point"]' 14 | ); 15 | insertionPoint = emotionInsertionPoint ?? undefined; 16 | } 17 | 18 | return createCache({ key: "mui-style", insertionPoint }); 19 | } 20 | -------------------------------------------------------------------------------- /client/src/utils/data.ts: -------------------------------------------------------------------------------- 1 | export type dataType = { 2 | username: string; 3 | platforms: { 4 | exists: boolean; 5 | platform: string; 6 | }[]; 7 | }; 8 | 9 | export const data: dataType = { 10 | username: "maxwell", 11 | platforms: [ 12 | { 13 | exists: true, 14 | platform: "twitter", 15 | }, 16 | { 17 | exists: true, 18 | platform: "youtube", 19 | }, 20 | ], 21 | }; 22 | 23 | // REST endpoint http://localhost:8080/aliases?username=maxwell 24 | -------------------------------------------------------------------------------- /client/src/utils/theme.ts: -------------------------------------------------------------------------------- 1 | import { red } from "@mui/material/colors"; 2 | import { createTheme } from "@mui/material/styles"; 3 | 4 | declare module "@mui/material/styles" { 5 | interface BreakpointOverrides { 6 | xs: true; 7 | sm: true; 8 | md: true; 9 | lg: true; 10 | xl: true; 11 | } 12 | } 13 | 14 | declare module "@mui/material/styles" { 15 | interface Theme { 16 | appDrawer: { 17 | width: React.CSSProperties["width"]; 18 | breakpoint: BreakpointOverrides; 19 | }; 20 | } 21 | interface ThemeOptions { 22 | appDrawer?: { 23 | width?: React.CSSProperties["width"]; 24 | breakpoint?: BreakpointOverrides; 25 | }; 26 | } 27 | } 28 | export const theme = createTheme({ 29 | breakpoints: { 30 | keys: ["xs", "sm", "md", "lg", "xl"], 31 | values: { 32 | xs: 368, 33 | sm: 425, 34 | md: 601, 35 | lg: 769, 36 | xl: 1024, 37 | }, 38 | }, 39 | components: { 40 | MuiTextField: { 41 | variants: [ 42 | { 43 | props: { 44 | variant: "outlined", 45 | }, 46 | style: { 47 | color: "red", 48 | // borderRadius: "0.3rem", 49 | input: { 50 | "&::placeholder": { 51 | color: "#FAB535", 52 | }, 53 | }, 54 | // "& .MuiOutlinedInput-input"; { 55 | // color: "red", 56 | 57 | // }, 58 | "& label": { 59 | color: "#FAB535", 60 | }, 61 | "& label.Mui-focused": { 62 | color: "#FAB535", 63 | }, 64 | "& .MuiOutlinedInput-root": { 65 | "& fieldset": { 66 | borderColor: "#FAB535", 67 | }, 68 | "&:hover fieldset": { 69 | borderColor: "#FAB535", 70 | }, 71 | "&.Mui-focused fieldset": { 72 | borderColor: "#FAB535", 73 | }, 74 | }, 75 | }, 76 | }, 77 | ], 78 | }, 79 | }, 80 | palette: { 81 | primary: { 82 | main: "#FAB535", 83 | contrastText: "#000", 84 | }, 85 | secondary: { 86 | light: "rgba(0,0,0,0.5)", 87 | main: "rgb(0,0,0)", 88 | }, 89 | error: { 90 | main: red.A400, 91 | }, 92 | }, 93 | }); 94 | export const lightTheme = createTheme({ 95 | palette: { 96 | primary: { main: "#FAB535" }, 97 | secondary: { main: "#09334F" }, 98 | mode: "light", 99 | }, 100 | }); 101 | export const darkTheme = createTheme({ 102 | palette: { 103 | primary: { main: "#FAB535" }, 104 | secondary: { main: "#09334F" }, 105 | mode: "dark", 106 | }, 107 | }); 108 | -------------------------------------------------------------------------------- /client/src/utils/userInfoContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, createContext, useContext } from "react"; 2 | import userInfoReducer, { initialState } from "./userInfoReducer"; 3 | 4 | const UserStateContext = createContext(initialState); 5 | 6 | /** 7 | * 8 | * @param {React.ReactNode} children 9 | * @returns JSX.Element 10 | */ 11 | 12 | /** 13 | * =========== USER_INFO_PROVIDER ============= 14 | * 15 | * The logic that surrounds fetching data from the server and 16 | * managing state informatin for the user cycle is house by the 17 | * UserInfoProvider 18 | * 19 | * All LOGIC should live in this space 20 | * 21 | * A unique ACTION for each dispatch should be set up in the 22 | * reducer fuction. 23 | * 24 | * For every ACION_TYPE that is created, ensure to include it to the 25 | * [IUserInfoReducerAction] in '../intefaces' 26 | * 27 | * =========== How To Add Logic ================ 28 | * [1] setup a function for your logic of choice 29 | * [2] include function to initialState in './userInfoReducer' 30 | * [3] include function to the value attribute of the 31 | * -: This is to make it aaccebile by 32 | * the context hook. 33 | */ 34 | 35 | export const UserInfoProvider = ({ 36 | children, 37 | }: { 38 | children: React.ReactNode; 39 | }) => { 40 | const [state, dispatch] = useReducer(userInfoReducer, initialState); 41 | 42 | //ADD YOU LOGIC HERE (see description for how to add logic) 43 | const setUsername = (username: string) => { 44 | dispatch({ 45 | type: "SET_USERNAME", 46 | payload: username, 47 | }); 48 | }; 49 | return ( 50 | 51 | {children} 52 | 53 | ); 54 | }; 55 | 56 | /** 57 | * 58 | * @returns { 59 | username: string; 60 | fname: string; 61 | lname: string; 62 | suggestion: string[]; 63 | data: string[]; 64 | * } 65 | * More return values will be provided according to use cases 66 | */ 67 | 68 | /** 69 | * To prevent import useContext hook in all areas where UserInfo would be needed 70 | * this context is created to expose the information available for the user 71 | * 72 | * import [useUserInfo] and expect the necessary user data 73 | */ 74 | 75 | export const useUserInfo = () => { 76 | const context = useContext(UserStateContext); 77 | 78 | if (!context) 79 | throw new Error(`useUserInfo must be used inside UserInfoProvider`); 80 | 81 | return context; 82 | }; 83 | -------------------------------------------------------------------------------- /client/src/utils/userInfoReducer.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent } from "react"; 2 | import { IUserInfoReducerAction } from "../interfaces"; 3 | 4 | /** ======== DefaultState =========== */ 5 | export const initialState = { 6 | username: "", 7 | fname: "", 8 | lname: "", 9 | suggestions: [""], 10 | data: [""], 11 | setUsername: (username: string) => {}, 12 | }; 13 | 14 | /** 15 | * 16 | * @param { 17 | username: string; 18 | fname: string; 19 | lname: string; 20 | suggestion: string[]; 21 | data: string[]; 22 | } state 23 | * @param { 24 | type: "SEARCH_USERNAME"; 25 | payload: { [key: string]: string } | string; 26 | } action 27 | * @returns state 28 | */ 29 | 30 | /** 31 | * =========== USER_INFO_REDUCER ============= 32 | * 33 | * Handles all state updates and manipulation based on dispatched 34 | * action(s) 35 | * 36 | * All LOGIC that does not involve state manipulation should 37 | * live in the [UserInfoContext] space 38 | * 39 | * A unique ACTION for each dispatch should be set up in the 40 | * reducer fuction. 41 | * 42 | * ======== HOW TO ADD NEW ACTION_TYPE =============== 43 | * [1] Decide on a name for the action [type = string] 44 | * [2] include 'decided name to the interface for {IUserInfoReducerAction} 45 | * in '../intefaces' 46 | * [3] In the switch block, create a CASE to handle your "Decided action type" 47 | * 48 | * HINT: 49 | * Provide a break; statement if your ACTION_TYPE does not use the return key, 50 | * This will prevent the switch case from malfunctioning. 51 | * 52 | * 53 | */ 54 | 55 | const userInfoReducer = ( 56 | state: InitialStateType, 57 | action: IUserInfoReducerAction 58 | ): InitialStateType => { 59 | const { type, payload } = action; 60 | 61 | switch (type) { 62 | case "SET_USERNAME": 63 | return { ...state, username: payload as string }; 64 | default: 65 | throw new Error(`No case for typ ${type} found in userInfoReduser`); 66 | } 67 | }; 68 | 69 | export type InitialStateType = typeof initialState; 70 | export default userInfoReducer; 71 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | // "baseUrl": ".", 18 | // "paths": { 19 | // "@/components/*": ["src/components/*"], 20 | // "@/pages/*": ["pages/*"], 21 | // "@/styles/*": ["styles/*"] 22 | // } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types.d.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /client/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*module.css" { 2 | const styles: { 3 | [className: string]: string; 4 | }; 5 | export default styles; 6 | } 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | client: 5 | container_name: client 6 | user: node 7 | build: 8 | context: ./client 9 | ports: 10 | - "3000:3000" 11 | volumes: 12 | - ./client:/usr/src/app/client 13 | - /usr/src/app/client/node_modules 14 | - /usr/src/app/client/.next 15 | depends_on: 16 | - server 17 | server: 18 | container_name: server 19 | restart: always 20 | build: 21 | context: . 22 | env_file: .env 23 | ports: 24 | - 8080:8080 25 | depends_on: 26 | - db 27 | db: 28 | image: postgres 29 | restart: always 30 | ports: 31 | - 5432:5432 32 | volumes: 33 | - dbvol:/var/lib/postgresql/data 34 | environment: 35 | - POSTGRES_USER=postgres 36 | - POSTGRES_PASSWORD=postgres 37 | volumes: 38 | dbvol: -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@jest/types" 2 | 3 | const config: Config.InitialOptions = { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | verbose: true, 7 | automock: true, 8 | } 9 | export default config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch", 11 | "test:ci": "jest --ci" 12 | }, 13 | "proxy": "http://server:5000/", 14 | "dependencies": { 15 | "@emotion/cache": "^11.10.2", 16 | "@emotion/react": "^11.9.3", 17 | "@emotion/server": "^11.10.0", 18 | "@emotion/styled": "^11.9.3", 19 | "@fortawesome/fontawesome-svg-core": "^6.2.0", 20 | "@fortawesome/free-brands-svg-icons": "^6.2.0", 21 | "@fortawesome/free-solid-svg-icons": "^6.2.0", 22 | "@fortawesome/react-fontawesome": "^0.2.0", 23 | "@mui/icons-material": "^5.8.4", 24 | "@mui/material": "^5.8.7", 25 | "@mui/styles": "^5.10.3", 26 | "install": "^0.13.0", 27 | "next": "12.1.6", 28 | "next-themes": "^0.2.1", 29 | "npm": "^8.19.2", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-icons": "^4.4.0", 33 | "react-toggle-dark-mode": "^1.1.0" 34 | }, 35 | "devDependencies": { 36 | "@testing-library/jest-dom": "^5.16.5", 37 | "@testing-library/react": "^13.4.0", 38 | "@testing-library/user-event": "^14.4.3", 39 | "@types/node": "18.0.0", 40 | "@types/react": "18.0.14", 41 | "@types/react-dom": "18.0.5", 42 | "eslint": "8.18.0", 43 | "eslint-config-next": "12.1.6", 44 | "jest": "^29.3.1", 45 | "jest-environment-jsdom": "^29.3.1", 46 | "typescript": "4.7.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/app.ts: -------------------------------------------------------------------------------- 1 | import express, { Express } from 'express'; 2 | import { createLogger, transports, format } from "winston"; 3 | import routeApi from './routes/Api'; 4 | import * as dotenv from 'dotenv'; 5 | 6 | dotenv.config(); 7 | 8 | export const logger = createLogger({ 9 | transports: [new transports.Console()], 10 | format: format.combine( 11 | format.splat(), 12 | format.colorize(), 13 | format.timestamp(), 14 | format.printf(({ timestamp, level, message }) => { 15 | return `[${timestamp}] ${level}: ${message}`; 16 | }) 17 | ), 18 | }); 19 | export const app: Express = express(); 20 | app.use('/', routeApi); 21 | 22 | // app.listen(port, () => { 23 | // logger.info(`⚡️[server]: Server is running at https://localhost:${port}`); 24 | // }); 25 | 26 | export default app; -------------------------------------------------------------------------------- /server/controllers/Alias.test.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | describe("Server.ts tests", () => { 3 | test("Math test", () => { 4 | expect(2 + 2).toBe(4); 5 | }); 6 | }); -------------------------------------------------------------------------------- /server/controllers/Alias.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import {TwitterPlatform} from '../platforms/twitter/twitter' 3 | import {YoutubePlatform} from "../platforms/youtube/youtube" 4 | import { IUsernameCheck } from "../platforms/platform"; 5 | 6 | class Alias{ 7 | 8 | public static async index(req: Request,res: Response): Promise{ 9 | 10 | const username:string|undefined=req.query.username?.toString(); 11 | if(username==undefined){ 12 | res.json({ 13 | error:"missing field", 14 | description:"The parameter username is missing." 15 | }) 16 | } 17 | 18 | //add all results to an array to run them in parallel 19 | const requests=[]; 20 | if (process.env.TWITTER_BEARER_TOKEN) requests.push(Alias.twitter(username)) 21 | if (process.env.YOUTUBE_API_KEY) requests.push(Alias.youtube(username)) 22 | 23 | const platforms= (await Promise.allSettled(requests)) 24 | .filter((x): x is PromiseFulfilledResult=>x.status=='fulfilled') 25 | .map(x=>x.value) 26 | .filter(f=>f) 27 | res.json({ 28 | "username": username, 29 | "platforms": platforms 30 | }) 31 | 32 | } 33 | public static async twitter(username: string | undefined): Promise{ 34 | const client=new TwitterPlatform() 35 | 36 | return await client.checkUsernameExists(username??"") 37 | } 38 | public static async youtube(username: string | undefined): Promise{ 39 | 40 | const client=new YoutubePlatform() 41 | 42 | return await client.checkUsernameExists(username??"") 43 | } 44 | 45 | } 46 | 47 | export default Alias; -------------------------------------------------------------------------------- /server/controllers/Healthcheck.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {Request, Response} from 'express'; 4 | import { 5 | StatusCodes, 6 | } from 'http-status-codes'; 7 | 8 | class Healthcheck { 9 | public static index (req: Request, res: Response): void { 10 | res.json({ 11 | "status": StatusCodes.OK 12 | }) 13 | } 14 | } 15 | 16 | export default Healthcheck; -------------------------------------------------------------------------------- /server/platforms/platform.ts: -------------------------------------------------------------------------------- 1 | export abstract class Platform{ 2 | name:string; 3 | 4 | constructor(name:string){ 5 | this.name=name 6 | } 7 | 8 | abstract checkUsernameExists(username:string):Promise 9 | } 10 | 11 | export interface IUsernameCheck{ 12 | exists:boolean; 13 | platform :string 14 | } -------------------------------------------------------------------------------- /server/platforms/twitter/twitter.ts: -------------------------------------------------------------------------------- 1 | import { TwitterApi } from "twitter-api-v2"; 2 | import { Platform,IUsernameCheck } from "../platform"; 3 | import { logger } from "../../app"; 4 | 5 | 6 | const TWITTER_BEARER_TOKEN:string|undefined=process.env.TWITTER_BEARER_TOKEN 7 | export class TwitterPlatform extends Platform{ 8 | constructor(){ 9 | super("twitter") 10 | } 11 | 12 | 13 | async checkUsernameExists(username: string): Promise{ 14 | 15 | let check :IUsernameCheck|undefined 16 | try { 17 | logger.info("Initializing twitter client") 18 | // Instantiate with desired auth type (here's Bearer v2 auth) 19 | const twitterClient = new TwitterApi(TWITTER_BEARER_TOKEN??""); 20 | 21 | // Tell typescript it's a readonly app 22 | const readOnlyClient = twitterClient.readOnly; 23 | 24 | logger.info("Checking existance of alias") 25 | // Play with the built in methods 26 | const user = async (username:string)=>{ 27 | const {data}= await readOnlyClient.v1.searchUsers(username) 28 | 29 | return data 30 | }; 31 | 32 | //search twitter for existance of the username 33 | const result=await user(username); 34 | 35 | logger.info("returning results") 36 | check={ exists:result.length>0,platform:this.name } 37 | 38 | } catch (error) { 39 | logger.error(error) 40 | } 41 | 42 | return check 43 | } 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /server/platforms/youtube/youtube.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Platform,IUsernameCheck } from "../platform"; 3 | import { logger } from "../../app"; 4 | 5 | type YoutubeResponse = { 6 | "id": string, 7 | "kind": string, 8 | "etag": string, 9 | "pageInfo": { 10 | "totalResults": number, 11 | "resultsPerPage": number 12 | }, 13 | "contentDetails": { 14 | "relatedPlaylists": { 15 | "likes": string, 16 | "favorites": string, 17 | "uploads": string, 18 | "watchHistory": string, 19 | "watchLater": string 20 | }, 21 | "googlePlusUserId": string 22 | }, 23 | } 24 | export class YoutubePlatform extends Platform{ 25 | constructor(){ 26 | super("youtube") 27 | } 28 | async checkUsernameExists(username: string): Promise { 29 | const config = { 30 | params:{ 31 | part: "contentDetails", 32 | forUsername: username, 33 | key: process.env.YOUTUBE_API_KEY 34 | } 35 | }; 36 | const url = "https://www.googleapis.com/youtube/v3/channels" 37 | let check:IUsernameCheck | undefined; 38 | 39 | try{ 40 | const { data, status } = await axios.get( 41 | url, config 42 | ) 43 | check={ exists: data.pageInfo.totalResults > 0, platform:this.name } 44 | logger.info("Youtube username %s was found", username); 45 | }catch(error){ 46 | logger.error(error); 47 | } 48 | return check; 49 | } 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /server/routes/Api.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import HealthcheckController from '../controllers/Healthcheck' 3 | import AliasController from '../controllers/Alias' 4 | 5 | const router = Router(); 6 | 7 | router.get('/health', HealthcheckController.index); 8 | router.get('/aliases', AliasController.index) 9 | 10 | export default router; -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | import {logger, app} from './app'; 2 | import * as dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | const port: string | undefined = process.env.PORT; 6 | app.listen(port, () => { 7 | logger.info(`⚡️[server]: Server is running at https://localhost:${port}`); 8 | }); 9 | 10 | module.exports = app; -------------------------------------------------------------------------------- /server/tests/Alias.test.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | describe("Server.ts tests", () => { 3 | test("Math test", () => { 4 | expect(5 + 2).toBe(7); 5 | }); 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["**/*.test.*", "**/__mocks__/*", "**/__tests__/*"] 4 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "types": ["node", "jest"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": false, 11 | "esModuleInterop": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": false, 18 | "outDir": "./dist" 19 | }, 20 | "compileOnSave": true, 21 | "include": ["server.ts", "server/**/*.ts", "server/**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------
{hint.description}