├── .all-contributorsrc ├── .coveragerc ├── .env.default ├── .gitbook.yaml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .netlify └── state.json ├── .travis.yml ├── README.md ├── backend ├── Procfile ├── actions │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_journal.py │ │ ├── 0003_auto_20190715_1748.py │ │ ├── 0004_journal_entry_type.py │ │ ├── 0005_auto_20190716_2242.py │ │ ├── 0006_journal_platform.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── api │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_game_name.py │ │ ├── 0003_auto_20190524_2144.py │ │ ├── 0004_auto_20190524_2253.py │ │ ├── 0005_remove_game_name.py │ │ ├── 0006_game_name.py │ │ ├── 0007_ratings.py │ │ ├── 0008_auto_20190525_1848.py │ │ ├── 0009_auto_20190525_2014.py │ │ ├── 0010_auto_20190528_1923.py │ │ ├── 0011_auto_20190615_1713.py │ │ └── __init__.py │ └── urls.py ├── backend │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── games │ ├── __init__.py │ ├── apps.py │ ├── fields.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20190619_2205.py │ │ ├── 0003_auto_20190627_1842.py │ │ ├── 0004_game_slug.py │ │ ├── 0005_game_cover_id.py │ │ ├── 0006_auto_20190710_1659.py │ │ ├── 0007_auto_20190710_1819.py │ │ ├── 0008_game_backdrop_id.py │ │ ├── 0009_auto_20190711_2107.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py ├── requirements.txt └── users │ ├── __init__.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20190524_2150.py │ ├── 0003_auto_20190524_2258.py │ ├── 0004_auto_20190525_1151.py │ ├── 0005_auto_20190530_0830.py │ ├── 0006_auto_20190604_2103.py │ ├── 0007_auto_20190604_2216.py │ ├── 0008_auto_20190615_1713.py │ ├── 0009_customuser_gravatar.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── docs ├── .gitbook │ └── assets │ │ ├── landing (1).png │ │ ├── landing (2).png │ │ ├── landing (3).png │ │ ├── landing (4).png │ │ ├── landing-4 (1).png │ │ ├── landing-4.png │ │ ├── landing.png │ │ ├── logo (1).png │ │ ├── logo (2).png │ │ ├── logo (3).png │ │ ├── logo (4).png │ │ ├── logo-4 (1).png │ │ ├── logo-4.png │ │ └── logo.png ├── README.md ├── SUMMARY.md ├── api │ ├── actions.md │ ├── games.md │ └── users.md ├── community │ ├── code_of_conduct.md │ └── license.md └── getting-started │ ├── code_style.md │ ├── contributing.md │ └── setup.md ├── frontend ├── .env.default ├── .gitignore ├── package-lock.json ├── package.json ├── public │ ├── calendar.svg │ ├── earth.ico │ ├── earth.svg │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── semantic.json └── src │ ├── Router.js │ ├── index.js │ ├── index.scss │ ├── modules │ ├── app │ │ ├── actionTypes.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── backdrop │ │ │ │ ├── Backdrop.test.js │ │ │ │ ├── BackdropFrom.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── cover │ │ │ │ ├── Cover.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ ├── error │ │ │ │ └── index.js │ │ │ ├── errors │ │ │ │ ├── Error.test.js │ │ │ │ └── index.js │ │ │ ├── footer │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── index.js │ │ │ ├── list-preview │ │ │ │ ├── ListPreview.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── loaders │ │ │ │ ├── CoverLoader.js │ │ │ │ └── index.js │ │ │ ├── login │ │ │ │ ├── Form.js │ │ │ │ ├── SignInPage.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── navbar │ │ │ │ ├── Navbar.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── private-route │ │ │ │ └── index.js │ │ │ ├── ratings │ │ │ │ ├── Ratings.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── register │ │ │ │ ├── Form.js │ │ │ │ ├── SignUpPage.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ └── search │ │ │ │ ├── ResultRenderer.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ ├── hooks │ │ │ └── useBackdrop.js │ │ ├── index.js │ │ └── reducers.js │ ├── developer │ │ ├── components │ │ │ ├── company-details │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ ├── company-games │ │ │ │ └── index.js │ │ │ ├── company-logo │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ └── index.js │ │ └── index.js │ ├── frontpage │ │ ├── components │ │ │ ├── game-list │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ ├── games-board │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ └── index.js │ │ ├── index.js │ │ └── styles.js │ ├── game │ │ ├── actionTypes.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── actions │ │ │ │ ├── Buttons.js │ │ │ │ ├── Rating.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── details │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── index.js │ │ │ ├── loaders │ │ │ │ ├── ActionsLoader.js │ │ │ │ ├── CoverLoader.js │ │ │ │ ├── TextLoader.js │ │ │ │ ├── TileCoverLoader.js │ │ │ │ └── TitleLoader.js │ │ │ ├── log-modal │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── quick-stats │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ └── screenshots │ │ │ │ ├── Screenshot.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ ├── index.js │ │ ├── reducers.js │ │ └── styles.scss │ ├── landing │ │ ├── actionTypes.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── features │ │ │ │ ├── Features.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── index.js │ │ │ └── popular │ │ │ │ ├── Popular.test.js │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ ├── index.js │ │ ├── reducers.js │ │ ├── styles.scss │ │ └── utils.js │ ├── profile │ │ ├── actionTypes.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── basic-profile │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── index.js │ │ │ ├── journal │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── profile-nav │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── recent-activity │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ └── stats │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ ├── index.js │ │ ├── reducers.js │ │ └── styles.scss │ └── settings │ │ ├── actionTypes.js │ │ ├── actions.js │ │ ├── components │ │ ├── choose-favorites │ │ │ ├── ChooseFavorites.test.js │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── edit-profile │ │ │ ├── EditProfile.test.js │ │ │ ├── index.js │ │ │ └── styles.scss │ │ └── index.js │ │ ├── index.js │ │ └── reducers.js │ ├── reducers.js │ ├── serviceWorker.js │ ├── setupTests.js │ ├── store.js │ └── tests │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── commands │ ├── Assertions.py │ ├── TestSteps.py │ └── Utils.py │ ├── data │ ├── config │ │ └── config.json │ └── test_data │ │ ├── log.json │ │ ├── login.json │ │ └── register.json │ ├── e2e │ ├── __init__.py │ ├── pytest.ini │ ├── test_Log.py │ └── test_Register.py │ └── page_objects │ ├── GamePage.py │ ├── LandingPage.py │ └── ProfilePage.py ├── media ├── demo.gif ├── favorites.gif ├── landing.png ├── log.gif └── overworld.png └── setup_run_backend.sh /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = backend/ 3 | omit = backend/venv/*, */migrations/*, */manage.py, */wsgi.py, */__init__.py, */urls.py, */settings.py 4 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | IGDB_KEY= 2 | DEBUG=True 3 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | 3 | structure: 4 | readme: README.md 5 | summary: SUMMARY.md 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### OSX ### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # VS Code 12 | .vscode/ 13 | 14 | # IntelliJ IDEA 15 | .idea/ 16 | 17 | # Thumbnails 18 | ._* 19 | 20 | # Files that might appear on external disk 21 | .Spotlight-V100 22 | .Trashes 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | 32 | ### Python ### 33 | # Byte-compiled / optimized / DLL files 34 | __pycache__/ 35 | *.py[cod] 36 | 37 | # C extensions 38 | *.so 39 | 40 | # Distribution / packaging 41 | .Python 42 | venv/ 43 | build/ 44 | backend/docs/_build/ 45 | develop-eggs/ 46 | dist/ 47 | downloads/ 48 | eggs/ 49 | lib/ 50 | lib64/ 51 | parts/ 52 | sdist/ 53 | var/ 54 | *.egg-info/ 55 | .installed.cfg 56 | *.egg 57 | 58 | # PyInstaller 59 | # Usually these files are written by a python script from a template 60 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 61 | *.manifest 62 | *.spec 63 | 64 | # Installer logs 65 | pip-log.txt 66 | pip-delete-this-directory.txt 67 | 68 | # Unit test / coverage reports 69 | htmlcov/ 70 | .tox/ 71 | .coverage 72 | .cache 73 | nosetests.xml 74 | coverage.xml 75 | 76 | # Translations 77 | *.mo 78 | *.pot 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | target/ 85 | 86 | 87 | ### Django ### 88 | *.log 89 | *.pot 90 | *.pyc 91 | __pycache__/ 92 | local_settings.py 93 | 94 | .env 95 | db.sqlite3 96 | -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "33b0b37b-b9ac-4077-9cd3-731e470a948a" 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | matrix: 3 | include: 4 | - language: python 5 | python: 6 | - "3.7" 7 | install: 8 | - pip install -r backend/requirements.txt 9 | - pip install psycopg2 --quiet 10 | - pip install codecov 11 | services: 12 | - postgresql 13 | before_script: 14 | - psql -c "CREATE DATABASE overworld;" -U postgres 15 | script: 16 | - python backend/manage.py migrate 17 | - coverage run backend/manage.py test games actions users 18 | after_success: 19 | - codecov 20 | - language: node_js 21 | node_js: 22 | - "8.11.0" 23 | before_script: 24 | - cd frontend/ 25 | - npm install 26 | - npm install -g codecov 27 | script: 28 | - npm test -- --coverage && codecov 29 | -------------------------------------------------------------------------------- /backend/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn backend.wsgi 2 | -------------------------------------------------------------------------------- /backend/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/actions/__init__.py -------------------------------------------------------------------------------- /backend/actions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ActionsConfig(AppConfig): 5 | name = 'actions' 6 | -------------------------------------------------------------------------------- /backend/actions/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-16 00:13 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('games', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Ratings', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('rating', models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])), 24 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Game')), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | options={ 28 | 'unique_together': {('game', 'user')}, 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /backend/actions/migrations/0002_journal.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-27 16:43 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('games', '0002_auto_20190619_2205'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('actions', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Journal', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('date', models.DateField()), 23 | ('review', models.TextField(null=True)), 24 | ('spoilers', models.BooleanField(null=True)), 25 | ('liked', models.BooleanField(null=True)), 26 | ('rating', models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])), 27 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Game')), 28 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /backend/actions/migrations/0003_auto_20190715_1748.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-15 17:48 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('actions', '0002_journal'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='journal', 16 | name='rating', 17 | field=models.DecimalField(decimal_places=1, max_digits=2, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(5)]), 18 | ), 19 | migrations.AlterField( 20 | model_name='ratings', 21 | name='rating', 22 | field=models.DecimalField(decimal_places=1, max_digits=2, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(5)]), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/actions/migrations/0004_journal_entry_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-16 17:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('actions', '0003_auto_20190715_1748'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='journal', 15 | name='entry_type', 16 | field=models.CharField(choices=[('F', 'Finished'), ('S', 'Started'), ('P', 'Played'), ('A', 'Abandoned')], default='F', max_length=1), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/actions/migrations/0005_auto_20190716_2242.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-16 22:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('actions', '0004_journal_entry_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='journal', 15 | name='entry_type', 16 | field=models.CharField(choices=[('F', 'Finished'), ('S', 'Started'), ('P', 'Played'), ('A', 'Abandoned'), ('R', 'Replayed')], default='F', max_length=1), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/actions/migrations/0006_journal_platform.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-17 16:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('actions', '0005_auto_20190716_2242'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='journal', 15 | name='platform', 16 | field=models.CharField(blank=True, max_length=64, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/actions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/actions/migrations/__init__.py -------------------------------------------------------------------------------- /backend/actions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.validators import MinValueValidator, MaxValueValidator 3 | 4 | 5 | class Ratings(models.Model): 6 | game = models.ForeignKey('games.Game', on_delete=models.CASCADE) 7 | user = models.ForeignKey('users.CustomUser', on_delete=models.CASCADE) 8 | rating = models.DecimalField(null=True, max_digits=2, decimal_places=1, 9 | validators=[MinValueValidator(0), MaxValueValidator(5)]) 10 | 11 | def __str__(self): 12 | return '%s - %s - %s' % (self.user, self.game, self.rating) 13 | 14 | class Meta: 15 | unique_together = ['game', 'user'] 16 | 17 | 18 | class Journal(models.Model): 19 | FINISHED = 'F' 20 | STARTED = 'S' 21 | PLAYED = 'P' 22 | ABANDONED = 'A' 23 | REPLAYED = 'R' 24 | 25 | ENTRY_TYPE_CHOICES = ( 26 | (FINISHED, 'Finished'), 27 | (STARTED, 'Started'), 28 | (PLAYED, 'Played'), 29 | (ABANDONED, 'Abandoned'), 30 | (REPLAYED, 'Replayed'), 31 | ) 32 | 33 | game = models.ForeignKey('games.Game', on_delete=models.CASCADE) 34 | user = models.ForeignKey('users.CustomUser', on_delete=models.CASCADE) 35 | date = models.DateField() 36 | platform = models.CharField(max_length=64, blank=True, null=True) 37 | entry_type = models.CharField(max_length=1, choices=ENTRY_TYPE_CHOICES, default=FINISHED) 38 | review = models.TextField(null=True) 39 | spoilers = models.BooleanField(null=True) 40 | liked = models.BooleanField(null=True) 41 | rating = models.DecimalField(null=True, max_digits=2, decimal_places=1, 42 | validators=[MinValueValidator(0), MaxValueValidator(5)]) 43 | 44 | def __str__(self): 45 | return '%s - %s - %s' % (self.date, self.game, self.user) 46 | -------------------------------------------------------------------------------- /backend/actions/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from games.serializers import GameSerializer 3 | from .models import Ratings, Journal 4 | 5 | 6 | class ActionSerializer(serializers.Serializer): 7 | game = serializers.IntegerField() 8 | user = serializers.IntegerField() 9 | action = serializers.CharField(max_length=16) 10 | value = serializers.BooleanField() 11 | 12 | 13 | class RatingSerializer(serializers.ModelSerializer): 14 | class Meta: 15 | model = Ratings 16 | fields = ('game', 'user', 'rating') 17 | 18 | 19 | class JournalSerializer(serializers.ModelSerializer): 20 | game = GameSerializer(read_only=True) 21 | class Meta: 22 | model = Journal 23 | fields = '__all__' 24 | -------------------------------------------------------------------------------- /backend/actions/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | urlpatterns = [ 6 | path('', views.Actions.as_view(), name='get-actions'), 7 | path('log/', views.Log.as_view(), name='log-game'), 8 | path('like/', views.Like.as_view(), name='like-game'), 9 | path('wishlist/', views.Wishlist.as_view(), name='add-to-wishlist'), 10 | path('backlog/', views.Backlog.as_view(), name='add-to-backlog'), 11 | path('ratings/', views.Rate.as_view(), name='rate-game'), 12 | path('journal/', views.JournalView.as_view(), name='journal'), 13 | path('favorites/', views.FavoriteGames.as_view(), name='get-favorites'), 14 | path('favorites/add/', views.AddFavoriteGame.as_view(), name='add-favorite'), 15 | path('favorites/remove/', views.RemoveFavoriteGame.as_view(), name='remove-favorite'), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/actions/utils.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | class AllowAnyGet(permissions.BasePermission): 4 | 5 | def has_permission(self, request, view): 6 | # allow all GET requests 7 | if request.method == 'GET': 8 | return True 9 | 10 | # Otherwise, only allow authenticated requests 11 | return request.user and request.user.is_authenticated 12 | -------------------------------------------------------------------------------- /backend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/api/__init__.py -------------------------------------------------------------------------------- /backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /backend/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-20 22:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Game', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('igdb', models.IntegerField()), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/api/migrations/0002_game_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-21 01:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='name', 16 | field=models.CharField(default='default', max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0003_auto_20190524_2144.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-24 21:44 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0002_game_name'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='game', 15 | old_name='igdb', 16 | new_name='gb', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0004_auto_20190524_2253.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-24 22:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0003_auto_20190524_2144'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='gb', 16 | field=models.CharField(max_length=16), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0005_remove_game_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-25 02:42 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0004_auto_20190524_2253'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='game', 15 | name='name', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/api/migrations/0006_game_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-25 04:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0005_remove_game_name'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='name', 16 | field=models.CharField(default='default', max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0007_ratings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-26 01:36 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('api', '0006_game_name'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Ratings', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('rating', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])), 22 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Game')), 23 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/api/migrations/0008_auto_20190525_1848.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-26 01:48 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('api', '0007_ratings'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='ratings', 17 | unique_together={('game', 'user')}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/0009_auto_20190525_2014.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-26 03:14 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0008_auto_20190525_1848'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='ratings', 16 | name='rating', 17 | field=models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/0010_auto_20190528_1923.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-29 02:23 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0009_auto_20190525_2014'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='game', 15 | old_name='gb', 16 | new_name='igdb', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0011_auto_20190615_1713.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-16 00:13 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0008_auto_20190615_1713'), 10 | ('api', '0010_auto_20190528_1923'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name='ratings', 16 | unique_together=None, 17 | ), 18 | migrations.RemoveField( 19 | model_name='ratings', 20 | name='game', 21 | ), 22 | migrations.RemoveField( 23 | model_name='ratings', 24 | name='user', 25 | ), 26 | migrations.DeleteModel( 27 | name='Game', 28 | ), 29 | migrations.DeleteModel( 30 | name='Ratings', 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/api/migrations/__init__.py -------------------------------------------------------------------------------- /backend/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | 4 | urlpatterns = [ 5 | path('games/', include('games.urls')), 6 | path('actions/', include('actions.urls')), 7 | path('users/', include('users.urls')), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/backend/__init__.py -------------------------------------------------------------------------------- /backend/backend/urls.py: -------------------------------------------------------------------------------- 1 | """backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/', include('api.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /backend/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | from dotenv import load_dotenv 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 15 | 16 | load_dotenv 17 | 18 | application = get_wsgi_application() 19 | -------------------------------------------------------------------------------- /backend/games/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/games/__init__.py -------------------------------------------------------------------------------- /backend/games/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GamesConfig(AppConfig): 5 | name = 'games' 6 | -------------------------------------------------------------------------------- /backend/games/fields.py: -------------------------------------------------------------------------------- 1 | _game_fields = [ 2 | 'cover.image_id', 3 | 'first_release_date', 4 | 'genres.name', 5 | 'involved_companies.developer', 6 | 'involved_companies.publisher', 7 | 'involved_companies.company.country', 8 | 'involved_companies.company.name', 9 | 'name', 10 | 'platforms.name', 11 | 'screenshots.image_id', 12 | 'slug', 13 | 'summary', 14 | 'themes.name', 15 | ] 16 | 17 | _company_game_fields = [ 18 | 'cover.image_id', 19 | 'first_release_date', 20 | 'name', 21 | 'slug', 22 | ] 23 | 24 | _company_fields = [ 25 | 'country', 26 | 'description', 27 | 'developed', 28 | 'logo', 29 | 'published', 30 | 'slug', 31 | 'start_date', 32 | 'url', 33 | 'websites', 34 | 'name', 35 | ] 36 | 37 | _company_logo_fields = [ 38 | 'height', 39 | 'image_id', 40 | 'url', 41 | 'width', 42 | ] 43 | 44 | _recent_game_fields = [ 45 | 'cover.image_id', 46 | 'first_release_date', 47 | 'name', 48 | 'slug', 49 | ] 50 | 51 | _upcoming_game_fields = [ 52 | 'cover.image_id', 53 | 'first_release_date', 54 | 'name', 55 | 'release_dates.category', 56 | 'slug', 57 | ] 58 | 59 | _search_fields = [ 60 | 'cover.image_id', 61 | 'first_release_date', 62 | 'name', 63 | 'screenshots.image_id', 64 | 'slug', 65 | ] 66 | 67 | _popular_fields = [ 68 | 'game.cover.image_id', 69 | 'game.name', 70 | 'date', 71 | 'game.slug', 72 | ] 73 | 74 | _backdrop_fields = [ 75 | 'name', 76 | 'screenshots.image_id', 77 | 'slug', 78 | ] 79 | 80 | _genre_fields = { 81 | 'name', 82 | 'slug' 83 | } 84 | 85 | game_fields = ','.join(_game_fields) 86 | search_fields = ','.join(_search_fields) 87 | popular_fields = ','.join(_popular_fields) 88 | backdrop_fields = ','.join(_backdrop_fields) 89 | genre_fields = ','.join(_genre_fields) 90 | company_game_fields = ','.join(_company_game_fields) 91 | company_fields = ','.join(_company_fields) 92 | company_logo_fields = ','.join(_company_logo_fields) 93 | recents_fields = ','.join(_recent_game_fields) 94 | upcoming_fields = ','.join(_upcoming_game_fields) 95 | -------------------------------------------------------------------------------- /backend/games/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-16 00:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Game', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('igdb', models.CharField(max_length=16)), 19 | ('name', models.CharField(default='default', max_length=255)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/games/migrations/0002_auto_20190619_2205.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-19 22:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='igdb', 16 | field=models.IntegerField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0003_auto_20190627_1842.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-27 18:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0002_auto_20190619_2205'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='name', 16 | field=models.CharField(max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0004_game_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-28 18:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0003_auto_20190627_1842'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='slug', 16 | field=models.CharField(default='slug', max_length=255), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/games/migrations/0005_game_cover_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-09 16:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0004_game_slug'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='cover_id', 16 | field=models.CharField(max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0006_auto_20190710_1659.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-10 16:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0005_game_cover_id'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='cover_id', 16 | field=models.CharField(default='', max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0007_auto_20190710_1819.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-10 18:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0006_auto_20190710_1659'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='cover_id', 16 | field=models.CharField(max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0008_game_backdrop_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-11 16:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0007_auto_20190710_1819'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='backdrop_id', 16 | field=models.CharField(max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/0009_auto_20190711_2107.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-11 21:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('games', '0008_game_backdrop_id'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='backdrop_id', 16 | field=models.CharField(default='', max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/games/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/games/migrations/__init__.py -------------------------------------------------------------------------------- /backend/games/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Game(models.Model): 5 | igdb = models.IntegerField() 6 | name = models.CharField(max_length=255) 7 | slug = models.CharField(max_length=255) 8 | cover_id = models.CharField(max_length=255) 9 | backdrop_id = models.CharField(max_length=255, default='') 10 | 11 | def __str__(self): 12 | return '%s - %s' % (self.igdb, self.name) 13 | -------------------------------------------------------------------------------- /backend/games/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Game 3 | 4 | 5 | class GameSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Game 8 | fields = ('igdb', 'name', 'slug', 'cover_id', 'backdrop_id') 9 | -------------------------------------------------------------------------------- /backend/games/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | urlpatterns = [ 6 | path('frontpage/', views.get_frontpage_games, name='frontpage-games'), 7 | path('company//', views.get_company, name='get-company-games'), 8 | path('popular/', views.get_popular_games, name='get-popular'), 9 | path('genres/', views.get_genres, name='get-genres'), 10 | path('/', views.get_game, name='get-game'), 11 | path('/ratings', views.get_game_ratings, name='get-game-ratings'), 12 | path('search/', views.search_game, name='search-game'), 13 | path('backdrop//', views.get_backdrop, name='get-backdrop'), 14 | path('', views.get_games, name='get-games'), 15 | 16 | ] 17 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.4 2 | asn1crypto==1.4.0 3 | atomicwrites==1.4.0 4 | attrs==21.2.0 5 | certifi==2021.5.30 6 | cffi==1.14.5 7 | chardet==4.0.0 8 | colorama==0.4.4 9 | cryptography==3.4.7 10 | dj-database-url==0.5.0 11 | Django==3.2.4 12 | django-cors-headers==3.7.0 13 | django-heroku==0.3.1 14 | django-rest-framework==0.1.0 15 | django-rest-knox==4.1.0 16 | djangorestframework==3.12.4 17 | Faker==8.6.0 18 | gunicorn==20.1.0 19 | idna==3.2 20 | importlib-metadata==4.5.0 21 | libgravatar==1.0.0 22 | more-itertools==8.8.0 23 | packaging==20.9 24 | pluggy==0.13.1 25 | psycopg2==2.8.6 26 | py==1.10.0 27 | pycparser==2.20 28 | pyparsing==2.4.7 29 | pytest==6.2.4 30 | pytest-html==3.1.1 31 | pytest-metadata==1.11.0 32 | pytest-random-order==1.0.4 33 | python-dateutil==2.8.1 34 | python-dotenv==0.17.1 35 | pytz==2021.1 36 | requests==2.25.1 37 | selenium==3.141.0 38 | six==1.16.0 39 | sqlparse==0.4.1 40 | text-unidecode==1.3 41 | urllib3==1.26.5 42 | wcwidth==0.2.5 43 | whitenoise==5.2.0 44 | zipp==3.4.1 45 | -------------------------------------------------------------------------------- /backend/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/users/__init__.py -------------------------------------------------------------------------------- /backend/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /backend/users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm, UserChangeForm 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm): 7 | 8 | class Meta(UserCreationForm): 9 | model = CustomUser 10 | fields = ('username', 'email') 11 | 12 | class CustomUserChangeForm(UserChangeForm): 13 | 14 | class Meta: 15 | model = CustomUser 16 | fields = UserChangeForm.Meta.fields 17 | -------------------------------------------------------------------------------- /backend/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-05-07 21:33 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0011_update_proxy_permissions'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='CustomUser', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 26 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 32 | ('games', models.CharField(blank=True, max_length=255)), 33 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 34 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 35 | ], 36 | options={ 37 | 'verbose_name': 'user', 38 | 'verbose_name_plural': 'users', 39 | 'abstract': False, 40 | }, 41 | managers=[ 42 | ('objects', django.contrib.auth.models.UserManager()), 43 | ], 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /backend/users/migrations/0002_auto_20190524_2150.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-24 21:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0003_auto_20190524_2144'), 10 | ('users', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='customuser', 16 | name='games', 17 | ), 18 | migrations.AddField( 19 | model_name='customuser', 20 | name='games', 21 | field=models.ManyToManyField(to='api.Game'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/users/migrations/0003_auto_20190524_2258.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-25 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0006_game_name'), 10 | ('users', '0002_auto_20190524_2150'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='customuser', 16 | name='games', 17 | ), 18 | migrations.AddField( 19 | model_name='customuser', 20 | name='liked', 21 | field=models.ManyToManyField(related_name='liked_games', to='api.Game'), 22 | ), 23 | migrations.AddField( 24 | model_name='customuser', 25 | name='played', 26 | field=models.ManyToManyField(related_name='played_games', to='api.Game'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/users/migrations/0004_auto_20190525_1151.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-25 18:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0006_game_name'), 10 | ('users', '0003_auto_20190524_2258'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='customuser', 16 | name='backlog', 17 | field=models.ManyToManyField(related_name='backlog', to='api.Game'), 18 | ), 19 | migrations.AddField( 20 | model_name='customuser', 21 | name='wishlist', 22 | field=models.ManyToManyField(related_name='wishlist', to='api.Game'), 23 | ), 24 | migrations.AlterField( 25 | model_name='customuser', 26 | name='liked', 27 | field=models.ManyToManyField(related_name='liked', to='api.Game'), 28 | ), 29 | migrations.AlterField( 30 | model_name='customuser', 31 | name='played', 32 | field=models.ManyToManyField(related_name='played', to='api.Game'), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/users/migrations/0005_auto_20190530_0830.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-30 15:30 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0010_auto_20190528_1923'), 11 | ('users', '0004_auto_20190525_1151'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='customuser', 17 | name='favorites', 18 | field=models.ManyToManyField(related_name='favorites', to='api.Game'), 19 | ), 20 | migrations.AddField( 21 | model_name='customuser', 22 | name='followers', 23 | field=models.ManyToManyField(related_name='_customuser_followers_+', to=settings.AUTH_USER_MODEL), 24 | ), 25 | migrations.AddField( 26 | model_name='customuser', 27 | name='following', 28 | field=models.ManyToManyField(related_name='_customuser_following_+', to=settings.AUTH_USER_MODEL), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /backend/users/migrations/0006_auto_20190604_2103.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-04 21:03 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0005_auto_20190530_0830'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='customuser', 16 | name='followers', 17 | field=models.ManyToManyField(related_name='fers', to=settings.AUTH_USER_MODEL), 18 | ), 19 | migrations.AlterField( 20 | model_name='customuser', 21 | name='following', 22 | field=models.ManyToManyField(related_name='fing', to=settings.AUTH_USER_MODEL), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/users/migrations/0007_auto_20190604_2216.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-04 22:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0006_auto_20190604_2103'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='customuser', 15 | name='bio', 16 | field=models.CharField(max_length=255, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='customuser', 20 | name='location', 21 | field=models.CharField(max_length=50, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='customuser', 25 | name='twitter', 26 | field=models.CharField(max_length=15, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/users/migrations/0008_auto_20190615_1713.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-06-16 00:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0007_auto_20190604_2216'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='customuser', 15 | name='backlog', 16 | field=models.ManyToManyField(related_name='backlog', to='games.Game'), 17 | ), 18 | migrations.AlterField( 19 | model_name='customuser', 20 | name='favorites', 21 | field=models.ManyToManyField(related_name='favorites', to='games.Game'), 22 | ), 23 | migrations.AlterField( 24 | model_name='customuser', 25 | name='liked', 26 | field=models.ManyToManyField(related_name='liked', to='games.Game'), 27 | ), 28 | migrations.AlterField( 29 | model_name='customuser', 30 | name='played', 31 | field=models.ManyToManyField(related_name='played', to='games.Game'), 32 | ), 33 | migrations.AlterField( 34 | model_name='customuser', 35 | name='wishlist', 36 | field=models.ManyToManyField(related_name='wishlist', to='games.Game'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/users/migrations/0009_customuser_gravatar.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-08 17:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0008_auto_20190615_1713'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='customuser', 15 | name='gravatar', 16 | field=models.CharField(max_length=255, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/backend/users/migrations/__init__.py -------------------------------------------------------------------------------- /backend/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from games.models import Game 4 | 5 | 6 | class CustomUser(AbstractUser): 7 | bio = models.CharField(max_length=255, null=True) 8 | twitter = models.CharField(max_length=15, null=True) 9 | location = models.CharField(max_length=50, null=True) 10 | gravatar = models.CharField(max_length=255, null=True) 11 | following = models.ManyToManyField('self', symmetrical=False, related_name='fing') 12 | followers = models.ManyToManyField('self', symmetrical=False, related_name='fers') 13 | played = models.ManyToManyField(Game, related_name='played') 14 | liked = models.ManyToManyField(Game, related_name='liked') 15 | backlog = models.ManyToManyField(Game, related_name='backlog') 16 | wishlist = models.ManyToManyField(Game, related_name='wishlist') 17 | favorites = models.ManyToManyField(Game, related_name='favorites') 18 | 19 | def __str__(self): 20 | return self.username 21 | -------------------------------------------------------------------------------- /backend/users/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate 2 | from rest_framework.validators import UniqueValidator 3 | from rest_framework import serializers 4 | from libgravatar import Gravatar 5 | from actions.models import Journal 6 | from games.serializers import GameSerializer 7 | from .models import CustomUser 8 | 9 | 10 | class UserSerializer(serializers.ModelSerializer): 11 | class Meta: 12 | model = CustomUser 13 | fields = ('id', 'username', 'email', 'gravatar') 14 | 15 | 16 | class ProfileSerializer(serializers.ModelSerializer): 17 | following = serializers.PrimaryKeyRelatedField( 18 | many=True, 19 | read_only=True 20 | ) 21 | 22 | followers = serializers.PrimaryKeyRelatedField( 23 | many=True, 24 | read_only=True 25 | ) 26 | 27 | class Meta: 28 | model = CustomUser 29 | depth = 1 30 | fields = ( 31 | 'id', 32 | 'username', 33 | 'email', 34 | 'bio', 35 | 'gravatar', 36 | 'location', 37 | 'twitter', 38 | 'played', 39 | 'liked', 40 | 'backlog', 41 | 'wishlist', 42 | 'favorites', 43 | 'following', 44 | 'followers', 45 | ) 46 | 47 | 48 | class RecentActivitySerializer(serializers.ModelSerializer): 49 | game = GameSerializer(read_only=True) 50 | class Meta: 51 | model = Journal 52 | fields = '__all__' 53 | 54 | 55 | class RegisterSerializer(serializers.ModelSerializer): 56 | password = serializers.CharField(min_length=8) 57 | email = serializers.EmailField( 58 | validators=[UniqueValidator(queryset=CustomUser.objects.all())]) 59 | 60 | class Meta: 61 | model = CustomUser 62 | fields = ('id', 'username', 'email', 'password') 63 | extra_kwargs = {'password': {'write_only': True}} 64 | 65 | def create(self, validated_data): 66 | g = Gravatar(validated_data['email']) 67 | gravatar = g.get_image(size=120, default='retro', use_ssl=True) 68 | user = CustomUser.objects.create_user( 69 | validated_data['username'], 70 | validated_data['email'], 71 | validated_data['password'], 72 | gravatar=gravatar 73 | ) 74 | return user 75 | 76 | 77 | class LoginSerializer(serializers.Serializer): 78 | username = serializers.CharField() 79 | password = serializers.CharField() 80 | 81 | def validate(self, data): 82 | user = authenticate(**data) 83 | if user and user.is_active: 84 | return user 85 | raise serializers.ValidationError('Incorrect username/password.') 86 | -------------------------------------------------------------------------------- /backend/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from knox import views as knox_views 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path('register/', views.RegisterView.as_view(), name='register'), 7 | path('login/', views.LoginView.as_view(), name='knox-login'), 8 | path('logout/', knox_views.LogoutView.as_view(), name='knox-logout'), 9 | path('logoutall/', knox_views.LogoutAllView.as_view(), name='knox-logoutall'), 10 | path('user/', views.UserView.as_view(), name='get-user'), 11 | path('profile/', views.ProfileView.as_view(), name='get-profile'), 12 | path('profile//activity', views.RecentActivityView.as_view(), name='activity'), 13 | path('profile//ratings', views.RatingsView.as_view(), name='get-ratings'), 14 | path('follow/', views.FollowView.as_view(), name='follow'), 15 | path('unfollow/', views.UnfollowView.as_view(), name='unfollow'), 16 | ] 17 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing (2).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing (3).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing (4).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing-4 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing-4 (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing-4.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/landing.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo (2).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo (3).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo (4).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo-4 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo-4 (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo-4.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/docs/.gitbook/assets/logo.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Welcome to Overworld! 3 | --- 4 | 5 | # Introduction 6 | 7 | ![](.gitbook/assets/logo-4%20%281%29.png) 8 | 9 | > ### _A_ [_letterboxd_](https://letterboxd.com) _for video games._ 10 | 11 | * [About](./#about) 12 | * [Inspiration](./#inspiration) 13 | * [Development](./#development) 14 | * [Contributing](./#contributing) 15 | * [Features](./#features) 16 | * [Acknowledgements](./#acknowledgements) 17 | 18 | ## About 19 | 20 | If you use Letterboxd you probably know what this project is about. If not... 21 | 22 | **Overworld** is a place where you can share your gaming experiences, keep track of what you play, discover games, manage your backlog, show love to your favorite games, write reviews, compile lists of specific games and _More Stuff™_. 23 | 24 | Follow other people to discuss or discover new games, or you can be a lone wolf and use it as a personal gaming journal. 25 | 26 | The project is still in early development. If you have any suggestions, [file an issue](https://github.com/danielgrijalva/overworld/issues/new/choose) or even better, [start contributing](getting-started/contributing.md)! 27 | 28 | ### Inspiration 29 | 30 | Overworld is _heavily_ inspired by [Letterboxd](https://letterboxd.com/), both in functionality, design and the overall user experience. 31 | 32 | ## Contributing 33 | 34 | Head over to the [contribution guidelines](getting-started/contributing.md) for more details. 35 | 36 | ## Features 37 | 38 | > #### [Demo](https://raw.githubusercontent.com/danielgrijalva/overworld/master/media/demo.gif) 39 | 40 | So far you can do the following: 41 | 42 | * Create an account 43 | * Log in/out 44 | * Search for games 45 | * View a game's details \(summary, platforms, crew, release date and more\) 46 | * Rate a game on a scale of 1 to 10 47 | * Add a game to your played games 48 | * Add a game to your backlog and/or wish list 49 | * Like a game 50 | 51 | ![](.gitbook/assets/landing-4%20%281%29.png) 52 | 53 | ## Acknowledgements 54 | 55 | Data from [`IGDB`](https://api.igdb.com). Icon by [`Freepik`](https://www.freepik.com/). 56 | 57 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Introduction](README.md) 4 | 5 | ## Getting Started 6 | 7 | * [Contributing](getting-started/contributing.md) 8 | * [Setup](getting-started/setup.md) 9 | * [Code Style](getting-started/code_style.md) 10 | 11 | ## API 12 | 13 | * [Users](api/users.md) 14 | * [Actions](api/actions.md) 15 | * [Games](api/games.md) 16 | 17 | ## Community 18 | 19 | * [Code of Conduct](community/code_of_conduct.md) 20 | * [License](community/license.md) 21 | 22 | -------------------------------------------------------------------------------- /docs/getting-started/code_style.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines and best practices used in Overworld 3 | --- 4 | 5 | # Code Style 6 | 7 | ## Python 8 | 9 | Please, follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide. For more information about the Python code style, refer to [The Hitchhiker’s Guide to Python](https://docs.python-guide.org/writing/style/). 10 | 11 | ## JavaScript 12 | 13 | We follow the [Airbnb style guide](https://github.com/airbnb/javascript) for JavaScript. React specific guidelines can be found [here](https://github.com/airbnb/javascript/tree/master/react). 14 | 15 | ## Commit style 16 | 17 | I personally use the [`gitmoji`](https://gitmoji.carloscuesta.me/) guide for my commit messages to keep them clean and simple. This is merely optional; your commits will be [_squashed_](https://github.blog/2016-04-01-squash-your-commits/) into master anyway 💁 18 | 19 | Please be thorough and specific with your commits. _Don't_ do anything like this: 20 | 21 | ```text 22 | git add . 23 | git commit -m 'updated a lot of stuff' 24 | ``` 25 | 26 | Instead, be more thoughtful and specific: 27 | 28 | ```text 29 | git add /path/to/file/ 30 | git commit -m 'quick summary of what you did' 31 | ``` 32 | 33 | I would go even further and add more details to the commit message with [`git commit --amend`](https://help.github.com/en/articles/changing-a-commit-message): 34 | 35 | ```text 36 | quick summary of what you did 37 | - added more fields to profile model 38 | - fixed bug where profile image wouldn't load 39 | - added documentation to README 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /docs/getting-started/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Want to help with the development of Overworld? 3 | --- 4 | 5 | # Contributing 6 | 7 | Awesome seeing you here! 🎉 please read this document in order to know how to ask questions, how to start working on something or how to set up the project on your machine. 8 | 9 | You will need to [fork this project](https://help.github.com/en/articles/fork-a-repo) and [submit a pull request](https://help.github.com/en/articles/about-pull-requests) with your contribution. If you are new to open source, check out [GitHub Flow](https://guides.github.com/introduction/flow/index.html) for a more detailed explanation on how the contributing process works. 10 | 11 | All contributors are expected to follow our [Code of Conduct](../community/code_of_conduct.md). Please make sure you are welcoming and friendly in our community. 12 | 13 | ## The process 14 | 15 | * To ask any question related to the project, propose a new feature or report a bug, [create a new issue](https://github.com/danielgrijalva/overworld/issues/new/choose). 16 | * Before contributing, take a look at the current [issues](https://github.com/danielgrijalva/overworld/issues) and comment on the ones you'll work on. 17 | * You will be assigned to the issue and all the code you submit will get reviewed before merging. 18 | 19 | ## Get in touch 20 | 21 | You can reach out to me on [Twitter](https://twitter.com/danielgrijalvas) or by [creating a new issue](https://github.com/danielgrijalva/overworld/issues/new/choose) ⚡ 22 | 23 | -------------------------------------------------------------------------------- /docs/getting-started/setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Installing Overworld in your machine 3 | --- 4 | 5 | # Setup 6 | 7 | The app is separated into two projects, `frontend` \([React](https://reactjs.org/)\) and `backend` \([Django REST Framework](https://www.django-rest-framework.org/)\). 8 | 9 | A few things to consider: 10 | 11 | * The frontend was bootsrapped using [`create-react-app`](https://github.com/facebook/create-react-app). 12 | * [`Redux`](https://redux.js.org/) is used to manage state across the app. 13 | * We use [`Semantic UI`](https://react.semantic-ui.com/) for UI components. 14 | * For user authentication, we use [`django-rest-knox`](https://github.com/James1345/django-rest-knox). 15 | 16 | ## Installation 17 | 18 | After forking the repository, do the following: 19 | 20 | * Obtain your API keys from [IGDB](https://api.igdb.com) and paste it inside your `.env` file. But first, copy the `.env.default` file to your actual `.env`: 21 | 22 | ```text 23 | cp .env.default .env 24 | ``` 25 | 26 | * Now, set up the backend: 27 | 28 | ```bash 29 | cd backend/ 30 | pip install -r requirements.txt 31 | python manage.py migrate 32 | python manage.py runserver 33 | ``` 34 | 35 | * Set up the frontend: 36 | 37 | ```bash 38 | cd frontend/ 39 | npm install 40 | npm start 41 | ``` 42 | 43 | As you can see, both the frontend and backend are two separate entities. To access the backend API go to `localhost:8000`, but most of the time you'll be using the main app \(the frontend\) by going to `localhost:3000`. 44 | 45 | -------------------------------------------------------------------------------- /frontend/.env.default: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://localhost:8000 2 | -------------------------------------------------------------------------------- /frontend/.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 | # semantic 9 | /semantic 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.21.1", 7 | "bitly-react": "^6.0.12", 8 | "font-awesome": "^4.7.0", 9 | "i18n-iso-countries": "^4.3.1", 10 | "moment": "^2.24.0", 11 | "node-sass": "^4.13.1", 12 | "nodemon": "^1.19.4", 13 | "prettier": "^1.19.1", 14 | "react": "^16.10.2", 15 | "react-content-loader": "^4.3.2", 16 | "react-copy-to-clipboard": "^5.0.2", 17 | "react-dates": "^21.2.1", 18 | "react-dom": "^16.10.2", 19 | "react-lazy-images": "^1.1.0", 20 | "react-moment": "^0.9.6", 21 | "react-rating": "^2.0.0", 22 | "react-redux": "^7.1.1", 23 | "react-router-dom": "^5.1.2", 24 | "react-scripts": "3.2.0", 25 | "react-show-more-text": "^1.2.3", 26 | "react-star-rating-component": "^1.4.1", 27 | "redux": "^4.0.4", 28 | "redux-devtools-extension": "^2.13.8", 29 | "redux-thunk": "^2.3.0", 30 | "semantic-ui-calendar-react": "^0.15.3", 31 | "semantic-ui-react": "^0.88.1", 32 | "styled-components": "^4.4.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/plugin-syntax-optional-chaining": "^7.8.3", 36 | "enzyme": "^3.10.0", 37 | "enzyme-adapter-react-16": "^1.15.1", 38 | "enzyme-to-json": "^3.4.2", 39 | "redux-mock-store": "^1.5.3" 40 | }, 41 | "scripts": { 42 | "start": "react-scripts start", 43 | "build": "react-scripts build", 44 | "test": "react-scripts test", 45 | "eject": "react-scripts eject", 46 | "format": "prettier \"src/**/*.{js,html,css}\" --write" 47 | }, 48 | "eslintConfig": { 49 | "extends": "react-app" 50 | }, 51 | "browserslist": [ 52 | ">0.2%", 53 | "not dead", 54 | "not ie <= 11", 55 | "not op_mini all" 56 | ], 57 | "jest": { 58 | "snapshotSerializers": [ 59 | "enzyme-to-json/serializer" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/public/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/public/earth.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/frontend/public/earth.ico -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgrijalva/overworld-old/0153556f94873cc6a6d08dd28e4b04d5cd09d9a8/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | Overworld 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/semantic.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "semantic/", 3 | "paths": { 4 | "source": { 5 | "config": "src/theme.config", 6 | "definitions": "src/definitions/", 7 | "site": "src/site/", 8 | "themes": "src/themes/" 9 | }, 10 | "output": { 11 | "packaged": "dist/", 12 | "uncompressed": "dist/components/", 13 | "compressed": "dist/components/", 14 | "themes": "dist/themes/" 15 | }, 16 | "clean": "dist/" 17 | }, 18 | "permission": false, 19 | "autoInstall": true, 20 | "rtl": false, 21 | "version": "2.8.6" 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/Router.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Switch, Route } from "react-router-dom"; 3 | import { Provider } from "react-redux"; 4 | import store from "./store"; 5 | import Game from "./modules/game/"; 6 | import { Navbar, SignUpPage, SignInPage } from "./modules/app/components/"; 7 | import App from "./modules/app/"; 8 | import Profile from "./modules/profile/"; 9 | import Settings from "./modules/settings/"; 10 | import { Developer } from "./modules/developer"; 11 | import { loadUser } from "./modules/app/actions"; 12 | 13 | const notFound = () => { 14 | return

Not Found

; 15 | }; 16 | 17 | class AppRouter extends React.Component { 18 | componentDidMount() { 19 | store.dispatch(loadUser()); 20 | } 21 | 22 | render() { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | export default AppRouter; 44 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import axios from "axios"; 4 | import "./index.scss"; 5 | import AppRouter from "./Router"; 6 | import * as serviceWorker from "./serviceWorker"; 7 | import ErrorBoundary from "./modules/app/components/error"; 8 | 9 | axios.defaults.baseURL = process.env.REACT_APP_API_URL; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById("root") 16 | ); 17 | 18 | // If you want your app to work offline and load faster, you can change 19 | // unregister() to register() below. Note this comes with some pitfalls. 20 | // Learn more about service workers: https://bit.ly/CRA-PWA 21 | serviceWorker.unregister(); 22 | -------------------------------------------------------------------------------- /frontend/src/modules/app/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const USER_LOADING = "USER_LOADING"; 2 | export const USER_LOADED = "USER_LOADED"; 3 | export const AUTH_ERROR = "AUTH_ERROR"; 4 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; 5 | export const LOGIN_FAIL = "LOGIN_FAIL"; 6 | export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS"; 7 | export const REGISTER_SUCCESS = "REGISTER_SUCCESS"; 8 | export const REGISTER_FAIL = "REGISTER_FAIL"; 9 | export const DISMISS_ERRORS = "DISMISS_ERRORS"; 10 | -------------------------------------------------------------------------------- /frontend/src/modules/app/actions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | USER_LOADED, 4 | USER_LOADING, 5 | AUTH_ERROR, 6 | LOGIN_SUCCESS, 7 | LOGIN_FAIL, 8 | LOGOUT_SUCCESS, 9 | REGISTER_SUCCESS, 10 | REGISTER_FAIL, 11 | DISMISS_ERRORS 12 | } from "./actionTypes"; 13 | 14 | export const loadUser = () => (dispatch, getState) => { 15 | dispatch({ type: USER_LOADING }); 16 | 17 | axios 18 | .get("/api/users/user/", tokenConfig(getState)) 19 | .then(res => { 20 | dispatch({ 21 | type: USER_LOADED, 22 | payload: res.data 23 | }); 24 | }) 25 | .catch(error => { 26 | dispatch({ 27 | type: AUTH_ERROR 28 | }); 29 | }); 30 | }; 31 | 32 | export const login = (username, password) => dispatch => { 33 | axios 34 | .post("/api/users/login/", { 35 | username: username, 36 | password: password 37 | }) 38 | .then(res => { 39 | dispatch({ 40 | type: LOGIN_SUCCESS, 41 | payload: res.data 42 | }); 43 | }) 44 | .catch(error => { 45 | dispatch({ 46 | type: LOGIN_FAIL, 47 | payload: Object.values(error.response.data) 48 | }); 49 | }); 50 | }; 51 | 52 | export const logout = () => (dispatch, getState) => { 53 | axios 54 | .post("/api/users/logout/", null, tokenConfig(getState)) 55 | .then(res => { 56 | dispatch({ 57 | type: LOGOUT_SUCCESS, 58 | payload: res.data 59 | }); 60 | }) 61 | .catch(error => { 62 | dispatch({ 63 | type: AUTH_ERROR 64 | }); 65 | }); 66 | }; 67 | 68 | export const register = ({ email, username, password }) => dispatch => { 69 | axios 70 | .post("/api/users/register/", { 71 | email: email, 72 | username: username, 73 | password: password 74 | }) 75 | .then(res => { 76 | dispatch({ 77 | type: REGISTER_SUCCESS, 78 | payload: res.data 79 | }); 80 | }) 81 | .catch(error => { 82 | dispatch({ 83 | type: REGISTER_FAIL, 84 | payload: 85 | error.response.status === 404 86 | ? ["Something happened, please try again later."] 87 | : Object.values(error.response.data) 88 | }); 89 | }); 90 | }; 91 | 92 | export const dismissErrors = () => dispatch => { 93 | dispatch({ type: DISMISS_ERRORS }); 94 | }; 95 | 96 | export const tokenConfig = getState => { 97 | const token = getState().auth.token; 98 | 99 | const config = { 100 | headers: { 101 | "Content-Type": "application/json" 102 | } 103 | }; 104 | 105 | if (token) { 106 | config.headers["Authorization"] = `Token ${token}`; 107 | } 108 | 109 | return config; 110 | }; 111 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/backdrop/Backdrop.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Enzyme, { shallow } from "enzyme"; 3 | import Backdrop from "../backdrop"; 4 | import { LazyImage } from "react-lazy-images"; 5 | 6 | describe("Test rendering", () => { 7 | it("renders without crashing", () => { 8 | const imageId = "nz70mpjc7kszcyesgmqw"; 9 | const wrap = shallow(); 10 | expect(wrap.length).toEqual(1); 11 | }); 12 | 13 | it("renders null with blank imageId prop", () => { 14 | const imageId = ""; 15 | const wrap = shallow(); 16 | expect(wrap.get(0)).toBeNull(); 17 | expect(wrap.find("div")).toHaveLength(0); 18 | }); 19 | 20 | it("renders wrapper with two children, LazyImage and backdrop-mask", () => { 21 | const imageId = "nz70mpjc7kszcyesgmqw"; 22 | const wrap = shallow(); 23 | const backdropWrapper = wrap.find("div.backdrop-wrapper"); 24 | expect(backdropWrapper).toHaveLength(1); 25 | expect(backdropWrapper.children()).toHaveLength(2); 26 | expect(backdropWrapper.childAt(0).find(LazyImage)).toHaveLength(1); 27 | expect(backdropWrapper.childAt(1).type()).toEqual("div"); 28 | expect(backdropWrapper.childAt(1).hasClass("backdrop-mask")).toEqual(true); 29 | }); 30 | }); 31 | 32 | describe("Test LazyImage rendering", () => { 33 | let wrap, imageId, expectedSrc, lazyImage; 34 | 35 | beforeAll(() => { 36 | imageId = "nz70mpjc7kszcyesgmqw"; 37 | expectedSrc = `https://images.igdb.com/igdb/image/upload/t_1080p/${imageId}.jpg`; 38 | wrap = shallow(); 39 | lazyImage = wrap.find(LazyImage); 40 | }); 41 | 42 | it("renders LazyImage with correct src when imageId is provided", () => { 43 | expect(lazyImage).toHaveLength(1); 44 | expect(lazyImage.props().src).toEqual(expectedSrc); 45 | }); 46 | 47 | it("renders LazyImage actual function with correct className and url", () => { 48 | const lazyImageActual = lazyImage.props().actual(); 49 | expect(lazyImageActual.props.className).toEqual("backdrop-actual"); 50 | expect(lazyImageActual.props.style.backgroundImage).toEqual( 51 | `url(${expectedSrc})` 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/backdrop/BackdropFrom.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Link } from "react-router-dom"; 4 | import "./styles.scss"; 5 | 6 | export default function BackdropFrom({ backdrop, position = false }) { 7 | return ( 8 | <> 9 | {Object.keys(backdrop).length > 0 && ( 10 |
14 | Backdrop from{" "} 15 | 21 | {backdrop.name} 22 | 23 |
24 | )} 25 | 26 | ); 27 | } 28 | 29 | BackdropFrom.propTypes = { 30 | backdrop: PropTypes.object.isRequired 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/backdrop/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Container } from "semantic-ui-react"; 4 | import { LazyImage } from "react-lazy-images"; 5 | import "./styles.scss"; 6 | 7 | export default function Backdrop({ imageId, position = false }) { 8 | const thumb = `https://images.igdb.com/igdb/image/upload/t_cover_small/${imageId}.jpg`; 9 | const actual = `https://images.igdb.com/igdb/image/upload/t_1080p/${imageId}.jpg`; 10 | 11 | if (imageId) { 12 | return ( 13 | 21 |
22 |
28 | ( 31 |
39 | )} 40 | actual={() => ( 41 |
48 | )} 49 | /> 50 |
51 |
52 |
53 | 54 | ); 55 | } 56 | return null; 57 | } 58 | 59 | Backdrop.propTypes = { 60 | imageId: PropTypes.string.isRequired 61 | }; 62 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/cover/Cover.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { shallow } from "enzyme"; 3 | import Cover from "../cover"; 4 | 5 | describe("Test rendering", () => { 6 | let wrap, imageId, slug, size; 7 | 8 | beforeAll(() => { 9 | imageId = "sylfmtzktmb4b0ext8nr"; 10 | slug = "dark-souls"; 11 | size = "big"; 12 | wrap = shallow(); 13 | }); 14 | 15 | it("renders without crashing", () => { 16 | expect(wrap.length).toEqual(1); 17 | }); 18 | 19 | it("renders cover", () => { 20 | expect(wrap.children().length).toEqual(1); 21 | }); 22 | 23 | it("renders Link with correct slug", () => { 24 | const linkWrap = wrap.childAt(0); 25 | 26 | expect(linkWrap.children().length).toEqual(1); 27 | expect(linkWrap.props().to).toEqual(`/games/${slug}`); 28 | }); 29 | 30 | it("renders Image with correct src", () => { 31 | const imageWrap = wrap.childAt(0).childAt(0); 32 | const expectedSrc = `https://images.igdb.com/igdb/image/upload/t_cover_${size}/${imageId}.jpg`; 33 | 34 | expect(imageWrap.props().src).toEqual(expectedSrc); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/cover/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { CoverWrapper, ImageLink, StyledCoverImage } from "./styles"; 4 | 5 | const Cover = ({ imageId, slug, size }) => { 6 | const coverUrl = `https://images.igdb.com/igdb/image/upload/t_cover_big/${imageId}.jpg`; 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | Cover.propTypes = { 17 | imageId: PropTypes.string.isRequired 18 | }; 19 | 20 | export default Cover; 21 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/cover/styles.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Link } from "react-router-dom"; 3 | import { Image } from "semantic-ui-react"; 4 | 5 | export const StyledCoverImage = styled(Image)` 6 | &&& { 7 | box-shadow: 0 0 0 1px rgb(48, 56, 64); 8 | width: 100%; 9 | height: 100%; 10 | object-fit: cover; 11 | } 12 | `; 13 | 14 | export const ImageLink = styled(Link)` 15 | width: 100%; 16 | height: 100%; 17 | display: block; 18 | transition: background-color, border-color 0.2s linear; 19 | position: relative; 20 | 21 | &:after { 22 | content: ""; 23 | position: absolute; 24 | transition: border 0.1s linear; 25 | border-radius: 0.3125rem; 26 | } 27 | 28 | &:hover:after, 29 | &:focus:after { 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | border: 3px solid #40bcf4; 35 | } 36 | `; 37 | 38 | export const CoverWrapper = styled.div` 39 | width: ${props => wrapperSizes[props.size].default.width}; 40 | height: ${props => wrapperSizes[props.size].default.height}; 41 | 42 | /* if the covers are small (e.g. for the profile page), 43 | add the following styles as well */ 44 | ${props => 45 | props.size === "small" && 46 | ` 47 | text-align: center; 48 | display: inline-block; 49 | margin: 0 1.25rem 0.5rem 0; 50 | `} 51 | 52 | @media only screen and (max-width: 991px) and (min-width: 320px) { 53 | width: ${props => wrapperSizes[props.size].smallScreen.width}; 54 | height: ${props => wrapperSizes[props.size].smallScreen.height}; 55 | } 56 | 57 | @media only screen and (max-width: 1199px) and (min-width: 992px) { 58 | width: ${props => wrapperSizes[props.size].mediumScreen.width}; 59 | height: ${props => wrapperSizes[props.size].mediumScreen.height}; 60 | } 61 | `; 62 | 63 | const wrapperSizes = { 64 | tiny: { 65 | default: { 66 | width: "100px", 67 | height: "142.2px" 68 | } 69 | }, 70 | small: { 71 | default: { 72 | width: "135px", 73 | height: "192px" 74 | }, 75 | smallScreen: { 76 | width: "79px", 77 | height: "112.35px" 78 | }, 79 | mediumScreen: { 80 | width: "108px", 81 | height: "153.6px" 82 | } 83 | }, 84 | big: { 85 | default: { 86 | width: "245px", 87 | height: "347.05px" 88 | }, 89 | smallScreen: { 90 | width: "165px", 91 | height: "233.75px" 92 | }, 93 | mediumScreen: { 94 | width: "212px", 95 | height: "300.3px" 96 | } 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/error/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default class ErrorBoundary extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | // Update state so the next render will show the fallback UI. 11 | return { hasError: true }; 12 | } 13 | 14 | componentDidCatch(error, info) {} 15 | 16 | render() { 17 | if (this.state.hasError) { 18 | // You can render any custom fallback UI 19 | console.log(this.state); 20 | return

Something went wrong.

; 21 | } 22 | 23 | return this.props.children; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/errors/Error.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { shallow } from "enzyme"; 3 | import Error from "."; 4 | 5 | describe("", () => { 6 | let wrapper; 7 | const testMessage = "test"; 8 | const restProps = { 9 | testProp1: "testProp1", 10 | testProp2: "testProp2" 11 | }; 12 | 13 | beforeEach(() => { 14 | wrapper = shallow(); 15 | }); 16 | 17 | afterEach(() => { 18 | wrapper.unmount(); 19 | }); 20 | 21 | it("renders the passed error message", () => { 22 | expect(wrapper.html()).toContain(testMessage); 23 | }); 24 | 25 | it("passes any additional props to the Message component", () => { 26 | const actualProps = wrapper.find("Message").props(); 27 | Object.entries(restProps).map(([propKey, propValue]) => { 28 | expect(actualProps[propKey]).toEqual(propValue); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/errors/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Message } from "semantic-ui-react"; 4 | 5 | export default function Error({ message, ...props }) { 6 | return ( 7 | 8 | {message} 9 | 10 | ); 11 | } 12 | 13 | Error.propTypes = { 14 | message: PropTypes.string.isRequired 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/footer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container, List } from "semantic-ui-react"; 3 | import "./styles.scss"; 4 | 5 | const Footer = () => ( 6 |
7 | 8 | 9 | 10 | About 11 | 12 | 16 | Feedback 17 | 18 | 19 | Open Source 20 | 21 | 22 |

23 | Made with ❤ in Planet Earth. Data from{" "} 24 | IGDB. 25 |

26 |
27 |
28 | ); 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/footer/styles.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | background-color: #2c3440; 3 | padding: 2rem; 4 | display: block; 5 | margin-top: 3.5rem; 6 | 7 | .info { 8 | padding-top: 0 !important; 9 | color: #678 !important; 10 | 11 | a { 12 | padding-top: 0 !important; 13 | color: #678 !important; 14 | 15 | &:hover { 16 | color: #9ab !important; 17 | } 18 | } 19 | } 20 | 21 | .list a { 22 | font-size: 16px !important; 23 | font-weight: 700; 24 | color: #9ab !important; 25 | text-shadow: 0 0 7px rgba(0, 0, 0, 0.45); 26 | letter-spacing: 0.025rem; 27 | 28 | &:hover { 29 | color: #def !important; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Backdrop } from "./backdrop"; 2 | export { default as BackdropFrom } from "./backdrop/BackdropFrom"; 3 | export { default as Error } from "./errors"; 4 | export { default as Footer } from "./footer"; 5 | export { default as LogIn } from "./login"; 6 | export { default as Navbar } from "./navbar"; 7 | export { default as Register } from "./register"; 8 | export { default as GameSearch } from "./search"; 9 | export { default as ListPreview } from "./list-preview"; 10 | export { default as Cover } from "./cover"; 11 | export { default as Ratings } from "./ratings"; 12 | export { default as ListLoader } from "./loaders"; 13 | export { default as SignUpPage } from "./register/SignUpPage"; 14 | export { default as SignInPage } from "./login/SignInPage"; 15 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/list-preview/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import "./styles.scss"; 4 | 5 | const ListPreview = ({ games }) => ( 6 |
7 | 8 |
    9 | {games.slice(0, 5).map(g => { 10 | return ( 11 |
  • 12 |
    13 | {g.name} 18 |
    19 |
  • 20 | ); 21 | })} 22 |
23 | 24 |
25 | ); 26 | 27 | export default ListPreview; 28 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/list-preview/styles.scss: -------------------------------------------------------------------------------- 1 | .list-preview { 2 | background: #0c0e10; 3 | border-radius: 0.3125rem; 4 | margin-bottom: 2rem; 5 | } 6 | 7 | .list-link { 8 | display: block; 9 | transition: background-color, border-color 0.1s linear; 10 | position: relative; 11 | 12 | &:hover:after { 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | bottom: 0; 17 | border: 3px solid #40bcf4; 18 | } 19 | } 20 | 21 | .list-link::after { 22 | display: block; 23 | -webkit-transition: background-color, border-color 0.1s linear; 24 | -o-transition: background-color, border-color 0.1s linear; 25 | content: ""; 26 | position: absolute; 27 | transition: border 0.1s linear; 28 | z-index: 6; 29 | border-radius: 0.3125rem; 30 | } 31 | 32 | .cover-list { 33 | overflow: hidden; 34 | 35 | &.overlapped { 36 | width: auto; 37 | 38 | .cover-list-item { 39 | margin-right: -22px; 40 | float: left; 41 | box-shadow: 4px 0 11px #14181c; 42 | 43 | &:first-child { 44 | z-index: 5; 45 | } 46 | 47 | &:nth-of-type(2) { 48 | z-index: 4; 49 | } 50 | 51 | &:nth-of-type(3) { 52 | z-index: 3; 53 | } 54 | 55 | &:nth-of-type(4) { 56 | z-index: 2; 57 | } 58 | 59 | &:nth-of-type(5) { 60 | z-index: 1; 61 | } 62 | } 63 | } 64 | } 65 | 66 | .list-cover-wrapper { 67 | width: 80px; 68 | height: 113.7px; 69 | } 70 | 71 | .cover-list-item { 72 | display: inline-block; 73 | position: relative; 74 | overflow: hidden; 75 | border-radius: 0.3125rem; 76 | border: 1px solid rgba(123, 124, 126, 0.8); 77 | } 78 | 79 | @media only screen and(max-width: 991px) and(min-width: 320px) { 80 | .list-cover-wrapper { 81 | width: 55px; 82 | height: 78.2px; 83 | } 84 | } 85 | 86 | @media only screen and(max-width: 1199px) and(min-width: 992px) { 87 | .list-cover-wrapper { 88 | width: 67px; 89 | height: 95.2px; 90 | } 91 | } 92 | 93 | ul.cover-list { 94 | list-style: none; 95 | } 96 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/loaders/CoverLoader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContentLoader from "react-content-loader"; 3 | 4 | const CoverLoader = props => { 5 | const { rows, columns, coverHeight, coverWidth, padding } = props; 6 | const coverHeightWithPadding = coverHeight + padding; 7 | const coverWidthWithPadding = coverWidth + padding; 8 | const covers = Array(columns * rows).fill(1); 9 | return ( 10 | 17 | {covers.map((g, i) => { 18 | let vy = Math.floor(i / columns) * coverHeightWithPadding; 19 | let vx = 20 | (i * coverWidthWithPadding) % (columns * coverWidthWithPadding); 21 | return ( 22 | 30 | ); 31 | })} 32 | 33 | ); 34 | }; 35 | 36 | export default CoverLoader; 37 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/loaders/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContentLoader from "react-content-loader"; 3 | 4 | const ListLoader = () => ( 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | export default ListLoader; 24 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/login/Form.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { login } from "../../actions"; 3 | import Error from "../errors/"; 4 | import { Redirect, Link } from "react-router-dom"; 5 | import { useSelector, useDispatch } from "react-redux"; 6 | import { Button, Form } from "semantic-ui-react"; 7 | 8 | export const LoginForm = (props) => { 9 | const defaultState = { 10 | username: "", 11 | password: "" 12 | }; 13 | 14 | const [{ username, password }, setState] = useState(defaultState); 15 | const { errors, isAuthenticated, user } = useSelector(state => state.auth); 16 | const dispatch = useDispatch(); 17 | 18 | const handleChange = event => { 19 | const { name, value } = event.target; 20 | setState(prevState => ({ 21 | ...prevState, 22 | [name]: value 23 | })); 24 | }; 25 | 26 | const handleSubmit = event => { 27 | event.preventDefault(); 28 | dispatch(login(username, password)); 29 | }; 30 | 31 | const validateForm = () => username.length > 0 && password.length > 0; 32 | 33 | if (isAuthenticated) { 34 | return ; 35 | } 36 | 37 | return ( 38 | <> 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 62 |

63 | New to Overworld?{" "} 64 | 70 | Create an account 71 | 72 |

73 |
74 | {errors && 75 | errors.map((e, i) => { 76 | return ; 77 | })} 78 | 79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/login/SignInPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, Container } from "semantic-ui-react"; 3 | import { LoginForm } from "./Form"; 4 | import UseBackdrop from "../../hooks/useBackdrop"; 5 | import "./styles.scss"; 6 | 7 | const SignInPage = () => { 8 | const options = { position: ["isLeft"] }; 9 | const { Backdrop } = UseBackdrop(options); 10 | 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 21 | 22 |

Welcome Back

23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 | 31 | ); 32 | }; 33 | 34 | export default SignInPage; 35 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/login/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Modal, Header, Menu } from "semantic-ui-react"; 3 | import { dismissErrors } from "../../actions"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { LoginForm } from "./Form"; 6 | import "./styles.scss"; 7 | 8 | const LogIn = ({ loginText }) => { 9 | const defaultState = { 10 | open: false 11 | }; 12 | const [{ open }, setState] = useState(defaultState); 13 | const { errors} = useSelector(state => state.auth); 14 | const dispatch = useDispatch(); 15 | 16 | const handleOpen = () => 17 | setState(prevState => ({ ...prevState, open: true })); 18 | 19 | const handleClose = () => { 20 | setState(defaultState); 21 | 22 | if (errors) { 23 | dispatch(dismissErrors); 24 | } 25 | }; 26 | 27 | return ( 28 | } 33 | closeIcon 34 | className="register" 35 | > 36 | 37 | 38 |
Welcome back
39 |
40 | 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default LogIn; 47 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/login/styles.scss: -------------------------------------------------------------------------------- 1 | .landing-header .sign-in { 2 | cursor: pointer; 3 | color: #def; 4 | border-bottom: 1px solid #456; 5 | 6 | &:hover { 7 | color: #fff; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/navbar/Navbar.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { mount } from "enzyme"; 3 | import Navbar from "."; 4 | import * as reactRedux from "react-redux"; 5 | 6 | jest 7 | .spyOn(reactRedux, "useDispatch") 8 | .mockReturnValue(jest.fn(action => action())); 9 | jest.spyOn(reactRedux, "useStore").mockReturnValue({ getState: jest.fn() }); 10 | const useSelectorSpy = jest 11 | .spyOn(reactRedux, "useSelector") 12 | .mockReturnValue({ errors: [], user: null, isAuthenticated: false }); 13 | 14 | jest.mock("react-router-dom", () => ({ 15 | withRouter: component => component, 16 | Link: jest.fn() 17 | })); 18 | 19 | describe("", () => { 20 | let wrapper; 21 | 22 | const historyMock = { 23 | push: jest.fn() 24 | }; 25 | 26 | beforeEach(() => { 27 | wrapper = mount(); 28 | }); 29 | 30 | afterEach(() => { 31 | wrapper.unmount(); 32 | jest.clearAllMocks(); 33 | }); 34 | 35 | it("pushes to history when an item is clicked", () => { 36 | wrapper.find('[name="games"]').simulate("click"); 37 | expect(historyMock.push).toHaveBeenCalledTimes(1); 38 | expect(historyMock.push).toHaveBeenCalledWith("/games"); 39 | }); 40 | 41 | it("displays a dropdown when the user is authenticated", () => { 42 | useSelectorSpy.mockReturnValueOnce({ user: {}, isAuthenticated: true }); 43 | 44 | wrapper = mount(); 45 | 46 | expect(wrapper.find("Dropdown").length).toEqual(1); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/navbar/styles.scss: -------------------------------------------------------------------------------- 1 | .ui { 2 | &.inverted.menu { 3 | .active { 4 | font-weight: 700 !important; 5 | font-family: Raleway, sans-serif !important; 6 | } 7 | 8 | .pink.active.item:hover { 9 | background: #e03997 !important; 10 | 11 | text-shadow: none !important; 12 | } 13 | 14 | .violet.active.item:hover { 15 | background: #6435c9 !important; 16 | 17 | text-shadow: none !important; 18 | } 19 | 20 | .yellow.active.item:hover { 21 | background: #fbbe08f5 !important; 22 | 23 | text-shadow: none !important; 24 | } 25 | 26 | .orange.active.item:hover { 27 | background: #f2711c !important; 28 | 29 | text-shadow: none !important; 30 | } 31 | 32 | .green.active.item:hover { 33 | background: #21ba45 !important; 34 | 35 | text-shadow: none !important; 36 | } 37 | } 38 | 39 | &.secondary.inverted.menu .brand:hover { 40 | background: none !important; 41 | } 42 | } 43 | 44 | .navbar { 45 | @media (min-width: 1199px) { 46 | max-width: 1200px; 47 | } 48 | max-width: 100vw; 49 | margin: auto !important; 50 | z-index: 1; 51 | height: 75px; 52 | .grid { 53 | @media (max-width: 800px) { 54 | margin-left: -40px !important; 55 | } 56 | @media (max-width: 767px) { 57 | margin-left: -14px !important; 58 | } 59 | } 60 | &:hover .menu-bg { 61 | opacity: 0.85; 62 | } 63 | 64 | > .menu { 65 | > a.item { 66 | letter-spacing: 0.035rem; 67 | font-weight: 700; 68 | text-shadow: 0 0 7px rgba(0, 0, 0, 0.55); 69 | font-family: Raleway, sans-serif; 70 | } 71 | 72 | > .active.item { 73 | text-shadow: none !important; 74 | } 75 | 76 | } 77 | } 78 | 79 | .menu-bg { 80 | max-width: 100vw; 81 | position: absolute; 82 | z-index: 0; 83 | height: 95px; 84 | background-color: transparent; 85 | opacity: 0.75; 86 | background-repeat: repeat-x; 87 | transition: opacity 0.35s cubic-bezier(0.165, 0.85, 0.45, 1); 88 | background-image: linear-gradient(180deg, rgba(20, 24, 28, 1) 0, rgba(20, 24, 28, 0.95) 14%, rgba(20, 24, 28, 0.8) 30%, rgba(20, 24, 28, 0.6) 45%, rgba(20, 24, 28, 0.4) 55%, rgba(20, 24, 28, 0.2) 70%, rgba(20, 24, 28, 0.1) 83%, rgba(20, 24, 28, 0)) !important; 89 | } 90 | 91 | .text { 92 | letter-spacing: 0.035rem; 93 | font-weight: 700; 94 | text-shadow: 0 0 7px rgba(0, 0, 0, 0.55); 95 | font-family: Raleway, sans-serif; 96 | } 97 | 98 | .dropdown { 99 | .item > a { 100 | color: rgba(0, 0, 0, 0.87) !important; 101 | } 102 | 103 | > .menu > .item { 104 | font-family: Segoe UI, sans-serif; 105 | } 106 | } 107 | 108 | .brand h1 { 109 | margin-top: 0; 110 | font-weight: 900; 111 | margin-left: 5px; 112 | letter-spacing: 0.035rem; 113 | font-family: Raleway, sans-serif !important; 114 | text-shadow: 0 0 10px rgba(0, 0, 0, 0.55); 115 | } 116 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/private-route/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | 5 | const PrivateRoute = ({ component: Component, auth, ...props }) => ( 6 | { 9 | if (auth.isLoading) { 10 | return

Loading

; 11 | } else if (!auth.isAuthenticated) { 12 | return ; 13 | } else { 14 | return ; 15 | } 16 | }} 17 | /> 18 | ); 19 | 20 | const mapStateToProps = state => ({ 21 | auth: state.auth 22 | }); 23 | 24 | export default connect(mapStateToProps)(PrivateRoute); 25 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/ratings/Ratings.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { mount } from "enzyme"; 3 | import Ratings from "."; 4 | 5 | describe("", () => { 6 | let wrapper; 7 | 8 | beforeEach(() => { 9 | const props = { ratings: [], showAverage: false }; 10 | wrapper = mount(); 11 | }); 12 | 13 | afterEach(() => { 14 | wrapper.unmount(); 15 | }); 16 | 17 | it("shows nothing if there are no ratings", () => { 18 | expect(wrapper.html()).toBe(null); 19 | }); 20 | 21 | describe("with ratings passed in", () => { 22 | const ratings = [{ rating: 1 }, { rating: 5 }]; 23 | 24 | beforeEach(() => { 25 | wrapper.setProps({ ratings }); 26 | }); 27 | 28 | it("displays a rating chart", () => { 29 | expect(wrapper.find(".rating-chart").length).toBe(1); 30 | }); 31 | 32 | describe("and opting to show averages", () => { 33 | beforeEach(() => { 34 | wrapper.setProps({ showAverage: true }); 35 | }); 36 | 37 | it("shows the average rating", () => { 38 | expect(wrapper.find(".rating-average").html()).toContain("3"); 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/ratings/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Divider, Popup } from "semantic-ui-react"; 3 | import "./styles.scss"; 4 | 5 | const Ratings = ({ ratings, showAverage }) => { 6 | const processRatings = data => { 7 | let chartData = { 8 | "0.5": 0, 9 | "1.0": 0, 10 | "1.5": 0, 11 | "2.0": 0, 12 | "2.5": 0, 13 | "3.0": 0, 14 | "3.5": 0, 15 | "4.0": 0, 16 | "4.5": 0, 17 | "5.0": 0 18 | }; 19 | const total_length = data.length; 20 | let total_val = 0; 21 | data.forEach(val => { 22 | chartData[val.rating] += 1; 23 | total_val += parseFloat(val.rating); 24 | }); 25 | const average = total_val / total_length; 26 | return { chartData, average, total: total_length }; 27 | }; 28 | 29 | const stringifyStars = value => { 30 | let stars = "★".repeat(value); 31 | if (value % 1) { 32 | stars += "½"; 33 | } 34 | return stars; 35 | }; 36 | 37 | if (ratings.length > 0) { 38 | const { chartData, average, total } = processRatings(ratings); 39 | return ( 40 | 41 | Ratings 42 |
43 | 44 | 45 | 46 |
47 | {Object.keys(chartData).map((key, i) => { 48 | const height = total ? (chartData[key] * 100) / total : 0; 49 | return ( 50 | 64 | } 65 | /> 66 | ); 67 | })} 68 |
69 |
70 | {showAverage && ( 71 | {average.toFixed(1)} 72 | )} 73 | ★★★★★ 74 |
75 |
76 |
77 | ); 78 | } else { 79 | return null; 80 | } 81 | }; 82 | 83 | export default Ratings; 84 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/ratings/styles.scss: -------------------------------------------------------------------------------- 1 | .rating-chart { 2 | background-color: #14181c; 3 | display: flex; 4 | width: 100%; 5 | margin: 0 0.55rem; 6 | 7 | > .rating-bar { 8 | width: 10%; 9 | background-color: #435566; 10 | margin-top: auto; 11 | border-top-right-radius: 2px; 12 | border-top-left-radius: 2px; 13 | border-right: 1px solid #14181c; 14 | cursor: pointer; 15 | 16 | &:hover { 17 | background-color: #667788; 18 | margin-top: auto; 19 | } 20 | } 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .rating-average { 28 | margin-top: auto; 29 | font-size: 1.75rem; 30 | font-family: Lato, sans-serif; 31 | font-weight: 300; 32 | display: inline-block; 33 | } 34 | 35 | .rating-label { 36 | margin-top: auto; 37 | text-align: center; 38 | font-size: 13px; 39 | 40 | .star { 41 | color: rgba(247, 187, 8, 0.85); 42 | display: block; 43 | height: 15px; 44 | margin-top: auto; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/register/SignUpPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, Container } from "semantic-ui-react"; 3 | import { RegistrationForm } from "./Form"; 4 | import UseBackdrop from "../../hooks/useBackdrop"; 5 | import "./styles.scss"; 6 | 7 | const SignUpPage = () => { 8 | 9 | const options = { position: ['isBackground'] }; 10 | const { Backdrop } = UseBackdrop(options); 11 | 12 | return ( 13 | <> 14 | 15 | 16 | 17 |

Start your gaming journal now, it's free!

18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | ); 30 | }; 31 | 32 | export default SignUpPage; 33 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/register/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Modal, Button, Header } from "semantic-ui-react"; 3 | import { dismissErrors } from "../../actions"; 4 | import { RegistrationForm } from "./Form"; 5 | import { useSelector, useDispatch } from "react-redux"; 6 | import "./styles.scss"; 7 | 8 | const Register = () => { 9 | const defaultState = { 10 | open: false 11 | }; 12 | 13 | const [{ open }, setState] = useState( 14 | defaultState 15 | ); 16 | 17 | const dispatch = useDispatch(); 18 | const { errors } = useSelector(state => state.auth); 19 | 20 | const handleOpen = () => 21 | setState(prevState => ({ ...prevState, open: true })); 22 | 23 | const handleClose = () => { 24 | setState(defaultState); 25 | 26 | if (errors) { 27 | dispatch(dismissErrors()); 28 | } 29 | }; 30 | 31 | return ( 32 | 38 | Get Started 39 | 40 | } 41 | className="register" 42 | closeIcon 43 | > 44 | 45 | 46 |
Join Overworld
47 |
48 | 49 |
50 |
51 | ); 52 | }; 53 | 54 | export default Register; 55 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/register/styles.scss: -------------------------------------------------------------------------------- 1 | .ui { 2 | &.modal { 3 | background: #456; 4 | 5 | &.register > .content { 6 | background: #456; 7 | } 8 | } 9 | 10 | &.error.message { 11 | background-color: #f2711c; 12 | color: #fff; 13 | box-shadow: none; 14 | margin-top: 0; 15 | width: 100%; 16 | 17 | &:not(:last-child) { 18 | margin-bottom: 0.7rem; 19 | } 20 | } 21 | } 22 | 23 | .register { 24 | padding: 1rem; 25 | 26 | .close.icon { 27 | color: #def; 28 | top: 2rem; 29 | right: 1.5rem; 30 | } 31 | 32 | .button.positive { 33 | margin: 0.7rem 0 1.7rem; 34 | text-shadow: 0 0 7px rgba(0, 0, 0, 0.35); 35 | } 36 | } 37 | 38 | .description .ui.header { 39 | font-weight: 300; 40 | text-transform: uppercase; 41 | letter-spacing: 0.025rem; 42 | color: #def; 43 | margin: 0 0 0.75rem; 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/modules/app/components/search/ResultRenderer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Label } from "semantic-ui-react"; 4 | 5 | export const ResultRenderer = ({ id, name, first_release_date }) => { 6 | const year = new Date(first_release_date * 1000).getFullYear(); 7 | if (first_release_date) { 8 | return