Merge branch 'dev_unstable' of github.com:vvsu-rent-project/dev_rent into dev_unstable
This commit is contained in:
commit
0af62ee05d
|
@ -0,0 +1,10 @@
|
|||
APP_DEBUG=false
|
||||
APP_PORT=80
|
||||
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=pairent
|
||||
|
||||
DJANGO_KEY=
|
|
@ -28,7 +28,4 @@ local_settings.py
|
|||
db.sqlite3
|
||||
|
||||
# macOS moment
|
||||
pairent_backend/.DS_Store
|
||||
# macOS moment жиза
|
||||
.DS_Store
|
||||
|
||||
|
|
82
README.md
82
README.md
|
@ -1,76 +1,8 @@
|
|||
# Аренда квартир для студентов
|
||||
<h1 align='center'>Точка</h1>
|
||||
Добро пожаловать в репозиторий проекта "точка"
|
||||
|
||||
В проекте есть три ветки:
|
||||
|
||||
- main — в эту ветку загружается код после положительного тестирования и ревью.
|
||||
- dev_stable — в эту ветку загружается стабильный код, который будет использоваться в продашкене.
|
||||
- dev_unstable — основная ветка для разработчиков.
|
||||
|
||||
## ТУТ НАЗВАНЫ ПАПКИ ПОД РАЗРАБОТКУ И ФРОНТ В БЕК НЕ ЗАХОДИТ И НИЧЕГО НЕ МЕНЯЕТ!
|
||||
|
||||
## Как начать работать?
|
||||
|
||||
- Устанавливаем [python](https://www.python.org/downloads/) версия 3.11.1 и [git](https://git-scm.com/)
|
||||
- [Настройте git](https://tproger.ru/curriculum/git-guide/). В основном нужно только ввести имя пользователя и почту, пароль запросит при клонировании
|
||||
|
||||
Если не хотите заморачиваться с терминалом. Советую использовать [GitHub Desktop](https://desktop.github.com/)
|
||||
|
||||
### Клонирование проекта через терминал
|
||||
|
||||
Команды написаны под windows. Вводить в командную строку. Проверяйте, что в терминале вы находитесь в правильной директории
|
||||
|
||||
- Клонируем репозиторий
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vvsu-rent-project/dev_rent.github
|
||||
```
|
||||
|
||||
- В консоли переходим в папку проекта
|
||||
- Далее переходим на ветку unstable
|
||||
|
||||
```bash
|
||||
git switch dev_unstable
|
||||
```
|
||||
|
||||
### Клонирование проекта через GitHub Desktop
|
||||
|
||||
- Заходим в настройки
|
||||
|
||||
![Заходим в настройки](misc/1_step.png)
|
||||
|
||||
- Входим в аккаунт
|
||||
|
||||
![Входим в аккаунт](misc/2_step.png)
|
||||
|
||||
- Клонируем репозиторий
|
||||
|
||||
![Клонируем репозиторий](misc/3_step.png)
|
||||
|
||||
![Клонируем репозиторий](misc/4_step.png)
|
||||
|
||||
### Подготовка проекта к работе
|
||||
|
||||
- Открываем VS Code, открываем папку проекта и запускаем терминал
|
||||
- В терминале переходим в папку pairent_backend
|
||||
|
||||
```bash
|
||||
cd pairent_backend
|
||||
```
|
||||
|
||||
- Создаем виртуальное окружение
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
```
|
||||
|
||||
- Активируем виртуальное окружение. Активировать окружение нужно: когда запускаете сервер, устанавливаете зависимости, добавляете новое django приложение
|
||||
|
||||
```bash
|
||||
venv\Scripts\activate
|
||||
```
|
||||
|
||||
- После активации, устанавливаем зависимости.
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
# Запуск локального сайта
|
||||
1. Создайте `.env` файл, рекомендуем использовать для этого шаблон `.env.example`
|
||||
2. Сгенерируйте ключи `genkeys.sh`
|
||||
3. Постройте статичные файлы `build.sh`
|
||||
3. Поднимите контейнеры `docker-compose up -d`
|
|
@ -0,0 +1,74 @@
|
|||
#!/bin/sh
|
||||
|
||||
# ----- Start commands -----
|
||||
|
||||
fatal_err() {
|
||||
echo -e "\033[31mFatal error $*\033[0m"
|
||||
}
|
||||
|
||||
# ----- End commands -----
|
||||
|
||||
|
||||
|
||||
# ----- Start safeguards -----
|
||||
|
||||
if [[ "$1" != '-a' ]]; then
|
||||
fatal_err
|
||||
echo " This script will potentially break any existing instance of Pairent."
|
||||
echo " To execute this script, re-run it with option -a as first argument."
|
||||
exit -1
|
||||
fi
|
||||
|
||||
if ! [ -f .env ]; then
|
||||
fatal_err
|
||||
echo -e " No .env file was found."
|
||||
echo -e " Please use the \033[32m.env.example\033[0m to create a dotenv file:"
|
||||
echo -e " 1. \033[34mcp .env.example .env\033[0m"
|
||||
echo -e " 2. Edit your \033[32m.env\033[0m in your favourite editor"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# ----- End safeguards -----
|
||||
|
||||
|
||||
|
||||
# ----- Start bootstrap front -----
|
||||
|
||||
echo Building frontend static files...
|
||||
|
||||
cd pairent_frontend_react
|
||||
|
||||
NPMS=('npm' 'pnpm' 'yarn')
|
||||
NPM='None'
|
||||
|
||||
for n in ${NPMS[@]}; do
|
||||
if [ -x "$(command -v "$n")" ]; then
|
||||
NPM=$n
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$NPM" == 'None' ]]; then
|
||||
fatal_err
|
||||
echo ' No node package manager was found in your system.'
|
||||
echo ' Please install one of the following:'
|
||||
for n in ${NPMS[@]}; do
|
||||
echo -e " $n"
|
||||
done
|
||||
exit -1
|
||||
fi
|
||||
|
||||
rm -rf node_modules
|
||||
|
||||
$NPM install
|
||||
$NPM run build
|
||||
|
||||
DIST_FOLDER=$(realpath dist)
|
||||
|
||||
cd ..
|
||||
cp -r $DIST_FOLDER/* static
|
||||
cp $DIST_FOLDER/index.html pairent_backend/pairent_app/templates
|
||||
cp $DIST_FOLDER/../public/* static -r
|
||||
|
||||
echo Done building frontend static files
|
||||
|
||||
# ----- End bootstrap front -----
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -0,0 +1,49 @@
|
|||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml rss;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/x-javascript js;
|
||||
text/plain txt;
|
||||
text/x-component htc;
|
||||
text/mathml mml;
|
||||
image/png png;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/svg+xml svg svgz;
|
||||
application/java-archive jar war ear;
|
||||
application/mac-binhex40 hqx;
|
||||
application/pdf pdf;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/zip zip;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream eot;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
audio/mpeg mp3;
|
||||
audio/x-realaudio ra;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/x-flv flv;
|
||||
video/x-msvideo avi;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-mng mng;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
user nobody nobody;
|
||||
worker_processes 5;
|
||||
error_log logs/error.log;
|
||||
pid logs/nginx.pid;
|
||||
worker_rlimit_nofile 8192;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
include /etc/nginx/proxy.conf;
|
||||
index index.html index.htm;
|
||||
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] $status '
|
||||
'"$request" $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log logs/access.log main;
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
gzip on;
|
||||
server_names_hash_bucket_size 128; # this seems to be required for some vhosts
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location ~ ^/(api|admin)/ {
|
||||
proxy_pass http://back;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
try_files $uri $uri/ /;
|
||||
}
|
||||
|
||||
location ~ ^/\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
client_max_body_size 10m;
|
||||
client_body_buffer_size 128k;
|
||||
proxy_connect_timeout 90;
|
||||
proxy_send_timeout 90;
|
||||
proxy_read_timeout 90;
|
||||
proxy_buffers 32 4k;
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -0,0 +1,46 @@
|
|||
version: '3'
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- './conf/nginx:/etc/nginx'
|
||||
- './static:/var/www/html'
|
||||
restart: always
|
||||
ports:
|
||||
- '${APP_PORT:-80}:80'
|
||||
networks:
|
||||
pairent:
|
||||
aliases:
|
||||
- nginx
|
||||
depends_on:
|
||||
- back
|
||||
back:
|
||||
build:
|
||||
context: ./pairent_backend
|
||||
dockerfile: Dockerfile
|
||||
networks:
|
||||
pairent:
|
||||
aliases:
|
||||
- back
|
||||
volumes:
|
||||
- './pairent_backend:/opt/code'
|
||||
environment:
|
||||
DB_PASS: '${DB_PASSWORD}'
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: postgres
|
||||
networks:
|
||||
pairent:
|
||||
aliases:
|
||||
- db
|
||||
volumes:
|
||||
- './data/db:/etc/mysql'
|
||||
environment:
|
||||
POSTGRES_PASSWORD: '${DB_PASSWORD}'
|
||||
POSTGRES_DB: 'pairent_db'
|
||||
POSTGRES_USER: 'root'
|
||||
|
||||
networks:
|
||||
pairent:
|
||||
driver: bridge
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
|
||||
# ----- Start commands -----
|
||||
|
||||
fatal_err() {
|
||||
echo -e "\033[31mFatal error $*\033[0m"
|
||||
}
|
||||
|
||||
# ----- End commands -----
|
||||
|
||||
# ----- Start safeguards -----
|
||||
|
||||
if [[ "$1" != '-a' ]]; then
|
||||
fatal_err
|
||||
echo " This script will potentially break any existing instance of Pairent."
|
||||
echo " To execute this script, re-run it with option -a as first argument."
|
||||
exit -1
|
||||
fi
|
||||
|
||||
if ! [ -f .env ]; then
|
||||
fatal_err
|
||||
echo -e " No .env file was found."
|
||||
echo -e " Please use the \033[32m.env.example\033[0m to create a dotenv file:"
|
||||
echo -e " 1. \033[34mcp .env.example .env\033[0m"
|
||||
echo -e " 2. Edit your \033[32m.env\033[0m in your favourite editor"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v python3)" ]; then
|
||||
fatal_err
|
||||
echo -e " Python is not installed."
|
||||
echo -e " Please install Python 3.11 on your system"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# ----- End safeguards -----
|
||||
|
||||
|
||||
|
||||
# ----- Start bootstrap back -----
|
||||
|
||||
echo Generating keys...
|
||||
KEY=$(python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')
|
||||
sed -Ei "s/^DJANGO_KEY=.*$/DJANGO_KEY=/" .env
|
||||
sed -Ei "s/^DJANGO_KEY=.*$/DJANGO_KEY='$KEY'/" .env
|
||||
|
||||
DB_PASS=$(tr -dc A-Za-z0-9 </dev/urandom | head -c $(( $RANDOM % 32 + 32 )))
|
||||
sed -Ei "s/DB_PASSWORD=\w*/DB_PASSWORD=$DB_PASS/" .env
|
||||
|
||||
echo Using these keys:
|
||||
echo -e " \033[32mDjango key: \033[0m$KEY"
|
||||
echo -e " \033[32mDatabase key: \033[0m$DB_PASS"
|
||||
|
||||
ln -s .env pairent_backend/.env
|
||||
|
||||
# ----- End bootstrap back -----
|
||||
|
||||
exit
|
BIN
misc/1_step.png
BIN
misc/1_step.png
Binary file not shown.
Before Width: | Height: | Size: 47 KiB |
BIN
misc/2_step.png
BIN
misc/2_step.png
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
BIN
misc/3_step.png
BIN
misc/3_step.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
BIN
misc/4_step.png
BIN
misc/4_step.png
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,19 @@
|
|||
FROM python:alpine
|
||||
|
||||
# mysql mariadb-connector-c-dev
|
||||
|
||||
RUN apk add --no-cache postgresql-libs && \
|
||||
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
|
||||
mkdir /opt/code && \
|
||||
apk --purge del .build-deps
|
||||
|
||||
COPY requirements.txt /opt/code/requirements.txt
|
||||
WORKDIR /opt/code
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
USER nobody
|
||||
|
||||
COPY . /opt/code
|
||||
|
||||
CMD [ "/bin/sh", "docker-entry.sh" ]
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
PYTHON='/usr/bin/env python3'
|
||||
|
||||
$PYTHON manage.py migrate
|
||||
$PYTHON -m gunicorn -w 3 -b 0.0.0.0:80 pairent_backend.wsgi
|
|
@ -0,0 +1 @@
|
|||
templates/index.html
|
|
@ -1,32 +1,22 @@
|
|||
from rest_framework.request import Request
|
||||
|
||||
from django.http import HttpResponseBadRequest, HttpResponse, JsonResponse, HttpRequest
|
||||
|
||||
def VVSUAuthProxy(req: HttpRequest):
|
||||
proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):];
|
||||
|
||||
preq = requests.request(req.method, proxy, headers={
|
||||
'User-Agent': 'OIDC Client / Pairent',
|
||||
'Origin': 'http://pairent.vvsu.ru',
|
||||
'Referer': 'http://pairent.vvsu.ru'
|
||||
});
|
||||
from .models import User, AuthToken
|
||||
|
||||
resp = HttpResponse(preq.content);
|
||||
resp.headers['Content-Type'] = preq.headers['Content-Type'];
|
||||
import ipware as iplib, time, requests, uuid
|
||||
ipware = iplib.IpWare();
|
||||
|
||||
return resp;
|
||||
def client_ip(req: HttpRequest):
|
||||
return ipware.get_client_ip(req.META)[0].exploded;
|
||||
|
||||
def register(oid, provider_id, name):
|
||||
user = User(
|
||||
favorites_apartments='',
|
||||
comparison_apartments='',
|
||||
name=name,
|
||||
# date_of_birth=,
|
||||
about_me='',
|
||||
gender='?',
|
||||
phone='+00000',
|
||||
# email=,
|
||||
# telegram=,
|
||||
# discord=,
|
||||
# city=,
|
||||
role='s',
|
||||
photo_provider='VVSU',
|
||||
openid_addr=oid,
|
||||
|
@ -80,8 +70,36 @@ def verify_auth_token(key, ip):
|
|||
token.delete();
|
||||
return False;
|
||||
|
||||
if (token.expires > time.time()):
|
||||
if (token.expires < time.time()):
|
||||
token.delete();
|
||||
return False;
|
||||
|
||||
return True;
|
||||
|
||||
def auth_required(func):
|
||||
"""
|
||||
Use authorization for this route.
|
||||
"""
|
||||
def inner(req: HttpRequest, *args, **kwargs):
|
||||
|
||||
if ('Authorization' not in req.headers.keys()):
|
||||
return JsonResponse({'error': 'no auth token'});
|
||||
if (not verify_auth_token(req.headers['Authorization'], client_ip(req))):
|
||||
return JsonResponse({'error': 'auth token invalid or expired'});
|
||||
func(req, *args, **kwargs);
|
||||
|
||||
return inner;
|
||||
|
||||
def rest_auth_required(func):
|
||||
"""
|
||||
Use authorization for this restframework view.
|
||||
"""
|
||||
def inner(self, req: HttpRequest, *args, **kwargs):
|
||||
|
||||
if ('Authorization' not in req.headers.keys()):
|
||||
return JsonResponse({'error': 'no auth token'});
|
||||
if (not verify_auth_token(req.headers['Authorization'], client_ip(req))):
|
||||
return JsonResponse({'error': 'auth token invalid or expired'});
|
||||
func(self, req, *args, **kwargs);
|
||||
|
||||
return inner;
|
|
@ -0,0 +1,34 @@
|
|||
import django.core.validators
|
||||
from django.core.validators import RegexValidator, MaxValueValidator
|
||||
from django.db import migrations, models
|
||||
|
||||
from pairent_app.models import User
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pairent_app', '0005_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PsychTestAnswers',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на первый вопрос')),
|
||||
('second_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на второй вопрос')),
|
||||
('third_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на третий вопрос')),
|
||||
('fourth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на четвертый вопрос')),
|
||||
('fifth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на пятый вопрос')),
|
||||
('sixth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на шестой вопрос')),
|
||||
('seventh_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на седьмой вопрос')),
|
||||
('eighth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на восьмой вопрос')),
|
||||
('nineth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на девятый вопрос')),
|
||||
('tenth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на десятый вопрос')),
|
||||
('eleventh_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на одиннадцатый вопрос')),
|
||||
('twelfth_question', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Ответ на двенадцатый вопрос')),
|
||||
# ('users', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pairent_app.user', verbose_name='Пользователь')),
|
||||
('user', models.BigIntegerField(verbose_name='ID Пользователя')),
|
||||
],
|
||||
)
|
||||
]
|
|
@ -28,7 +28,11 @@ class PsychTestAddResultSerializer(serializers.ModelSerializer):
|
|||
class PublicUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ('favorites_apartments', 'comparison_apartments')
|
||||
exclude = (
|
||||
'favorites_apartments',
|
||||
'comparison_apartments',
|
||||
'openid_id',
|
||||
)
|
||||
|
||||
# class PsychTestReultsSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
|
|
|
@ -20,7 +20,7 @@ from .serializer import (ApartamentListSerializer,
|
|||
|
||||
from .authlib import *
|
||||
|
||||
import json, math, random, re, requests, oidc_client, base64, uuid, time, ipware as iplib
|
||||
import json, math, random, re, requests, base64, uuid, time, ipware as iplib
|
||||
ipware = iplib.IpWare();
|
||||
|
||||
class ApartamentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
@ -106,33 +106,21 @@ class PsychTestAddResultViewSet(viewsets.ViewSet):
|
|||
|
||||
return Response({'successfully': 'results post'})
|
||||
|
||||
# TODO: remake
|
||||
# class CompatibleUsersView(viewsets.ViewSet):
|
||||
# def list(self, req: Request):
|
||||
# user_data = dict(req.data);
|
||||
|
||||
# # TODO: Verify auth
|
||||
# vvsu_login = user_data['openid'];
|
||||
|
||||
# # Exclude already viewed users
|
||||
# exclude = [];
|
||||
# if ('exclude' in user_data.keys()):
|
||||
# exclude = user_data['exclude'];
|
||||
|
||||
# try:
|
||||
# validate_email(vvsu_login);
|
||||
# except ValidationError:
|
||||
# return Request({'error': 'bad login'}, 400);
|
||||
|
||||
# try:
|
||||
# this_user = User.objects.get(openid_addr=vvsu_login);
|
||||
# except User.DoesNotExist:
|
||||
# return Response({'error': 'user not found'}, 404);
|
||||
class CompatibleUsersView(viewsets.ViewSet):
|
||||
@rest_auth_required
|
||||
def list(self, req: Request):
|
||||
user_data = dict(req.data);
|
||||
|
||||
# try:
|
||||
# answers_this_user = PsychTestReultsSerializer(PsychTestAnswers.objects.get(user=this_user)).dict;
|
||||
# except PsychTestAnswers.DoesNotExist:
|
||||
# return Response({'error': 'answers not found'}, 404);
|
||||
token = AuthToken.objects.get(key=req.headers['authorization']);
|
||||
|
||||
this_user = User.objects.get(pk=token.user);
|
||||
|
||||
answers_this_user = None;
|
||||
try:
|
||||
answers_this_user = PsychTestReultsSerializer(PsychTestAnswers.objects.get(user=this_user.id)).dict;
|
||||
except PsychTestAnswers.DoesNotExist:
|
||||
return Response({'error': 'answers not found'}, 404);
|
||||
|
||||
# users_answers_query = PsychTestReultsSerializer(PsychTestAnswers.objects.all(), many=True).dict
|
||||
|
||||
|
@ -146,12 +134,8 @@ class PsychTestAddResultViewSet(viewsets.ViewSet):
|
|||
# if score / 12 * 100 > 30:
|
||||
# users.append(UserSerializer(User.objects.get(pk=user_answers[0])).data)
|
||||
|
||||
# # for user in users_query:
|
||||
# # if (abs(user.psych_test_result - score) < 20):
|
||||
# # users.append(PublicUserSerializer(user).data);
|
||||
|
||||
|
||||
# return Response(users);
|
||||
return Response(users);
|
||||
|
||||
class UserLogin(APIView):
|
||||
|
||||
|
@ -243,3 +227,17 @@ class UserGet(APIView):
|
|||
|
||||
return JsonResponse(PublicUserSerializer(user).data);
|
||||
|
||||
|
||||
def VVSUAuthProxy(req: HttpRequest):
|
||||
proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):];
|
||||
|
||||
preq = requests.request(req.method, proxy, headers={
|
||||
'User-Agent': 'OIDC Client / Pairent',
|
||||
'Origin': 'http://pairent.vvsu.ru',
|
||||
'Referer': 'http://pairent.vvsu.ru'
|
||||
});
|
||||
|
||||
resp = HttpResponse(preq.content);
|
||||
resp.headers['Content-Type'] = preq.headers['Content-Type'];
|
||||
|
||||
return resp;
|
|
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
|||
https://docs.djangoproject.com/en/4.1/ref/settings/
|
||||
"""
|
||||
|
||||
from dotenv import dotenv_values
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
|
@ -19,11 +20,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-=(7b+4ka=5jak-mokuh4#-6_14f58#^0kjwqsz7wyon$4i@sel'
|
||||
DOTENV = dotenv_values(BASE_DIR / '.env');
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = DOTENV['DJANGO_KEY']
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = True#DOTENV['APP_DEBUG'] == 'true'
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
@ -72,7 +74,9 @@ ROOT_URLCONF = 'pairent_backend.urls'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [
|
||||
'templates/'
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -93,15 +97,16 @@ WSGI_APPLICATION = 'pairent_backend.wsgi.application'
|
|||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'pairent_db',
|
||||
'USER': 'django',
|
||||
'PASSWORD': 'Budnya1924$',
|
||||
'HOST': '178.20.44.123',
|
||||
'PORT': '3306',
|
||||
'OPTIONS': {
|
||||
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
|
||||
}
|
||||
'USER': DOTENV['DB_USER'],
|
||||
'PASSWORD': DOTENV['DB_PASSWORD'],
|
||||
'HOST': DOTENV['DB_HOST'],
|
||||
'PORT': DOTENV['DB_PORT'],
|
||||
# 'OPTIONS': {
|
||||
# 'init_command': "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
|
||||
# 'charset': 'utf8mb4'
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,16 @@ Including another URLconf
|
|||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.template import loader
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.http import HttpResponse
|
||||
|
||||
def root_url(req):
|
||||
return HttpResponse(loader.get_template('index.html').render())
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/', include('pairent_app.urls')),
|
||||
path('', root_url)
|
||||
]
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
mysqlclient
|
||||
psycopg2-binary
|
||||
django
|
||||
djangorestframework
|
||||
django-cors-headers
|
||||
Pillow
|
||||
requests
|
||||
python-ipware
|
||||
python-ipware
|
||||
python-dotenv
|
||||
gunicorn
|
||||
factory-boy
|
|
@ -3,6 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"homepage": "/static/dist",
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"bootstrap": "^5.2.3",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,30 @@
|
|||
import { IAPIObject } from "./IAPIObject";
|
||||
|
||||
class APIToken extends IAPIObject {
|
||||
|
||||
static storage_key = 'pairent_api_key';
|
||||
|
||||
constructor(data) {
|
||||
super();
|
||||
this.user = data.user;
|
||||
this.key = data.key;
|
||||
this.expires = data.expires;
|
||||
this.ip = data.ip;
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
user;
|
||||
|
||||
/** @type {string} */
|
||||
key;
|
||||
|
||||
/** A Unix timestamp (when the token will expire)
|
||||
* @type {number}
|
||||
*/
|
||||
expires;
|
||||
|
||||
/** @type {string} */
|
||||
ip;
|
||||
}
|
||||
|
||||
export { APIToken };
|
|
@ -0,0 +1,72 @@
|
|||
import axios, { Axios } from "axios";
|
||||
|
||||
import { User } from "./User";
|
||||
import { APIToken } from './APIToken';
|
||||
|
||||
class ClientCreateOptions {
|
||||
/** Key used to access APIs, if you don't have user data
|
||||
* @type {APIToken?}
|
||||
*/
|
||||
key;
|
||||
|
||||
/** Current user's data
|
||||
* @type {User?}
|
||||
*/
|
||||
user;
|
||||
}
|
||||
|
||||
/**
|
||||
* An API client which is basically a User but with
|
||||
* API token and with access to private fields.
|
||||
*/
|
||||
class Client extends User {
|
||||
/** @type {APIToken} */
|
||||
key;
|
||||
|
||||
/** @type {string} */
|
||||
openid_id;
|
||||
|
||||
/** @type {string} */
|
||||
favorites_apartments;
|
||||
|
||||
/** @type {string} */
|
||||
comparison_apartments;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ClientCreateOptions} options
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
if (options.key === undefined & options.user === undefined) {
|
||||
throw new Error('Either key or user is required.');
|
||||
}
|
||||
|
||||
this.key = options.key;
|
||||
if (options.user) {
|
||||
for (const key in options.user) {
|
||||
this[key] = options.user[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} method
|
||||
* @param {import('axios').AxiosRequestConfig<any>} options
|
||||
*/
|
||||
fetchData(url, method, options) {
|
||||
axios.request({
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
...(options.headers ? options.headers : {}),
|
||||
'Authorization': this.key.key
|
||||
},
|
||||
|
||||
...options
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { IAPIObject } from "./IAPIObject";
|
||||
import { Client } from './Client';
|
||||
|
||||
class Tinder extends IAPIObject {
|
||||
/**
|
||||
*
|
||||
* @param {Client} client Client to use to create requests
|
||||
*/
|
||||
constructor(client) {
|
||||
super();
|
||||
this.client = client;
|
||||
this.viewed = [];
|
||||
}
|
||||
|
||||
getCompatible() {
|
||||
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||
import constants from '../constants';
|
||||
|
||||
import { IAPIObject } from './IAPIObject';
|
||||
import { APIToken } from './APIToken';
|
||||
|
||||
const { API_ROOT, api_path } = constants;
|
||||
|
||||
|
@ -16,33 +17,6 @@ class UserLoginResponse {
|
|||
id;
|
||||
}
|
||||
|
||||
class APIToken extends IAPIObject {
|
||||
|
||||
static storage_key = 'pairent_api_key';
|
||||
|
||||
constructor(data) {
|
||||
super();
|
||||
this.user = data.user;
|
||||
this.key = data.key;
|
||||
this.expires = data.expires;
|
||||
this.ip = data.ip;
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
user;
|
||||
|
||||
/** @type {string} */
|
||||
key;
|
||||
|
||||
/** A Unix timestamp (when the token will expire)
|
||||
* @type {number}
|
||||
*/
|
||||
expires;
|
||||
|
||||
/** @type {string} */
|
||||
ip;
|
||||
}
|
||||
|
||||
class User extends IAPIObject {
|
||||
|
||||
isLoggedIn() {
|
||||
|
|
|
@ -64,7 +64,7 @@ const Header = function () {
|
|||
</a>
|
||||
<VerticalLine />
|
||||
<Link to='/'>
|
||||
<PairentLogo>Pairent</PairentLogo>
|
||||
<PairentLogo>Точка.</PairentLogo>
|
||||
</Link>
|
||||
<Link to="/">
|
||||
<LocationButton>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import "./styles/PsychTestHeader.css";
|
||||
|
||||
const PsychTestHeader = () => {
|
||||
return (
|
||||
<div className="header">
|
||||
<button type="button" className="header__btn">
|
||||
Вернуться назад
|
||||
</button>
|
||||
<h1 className="header__title">Тест на совместимость</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PsychTestHeader;
|
|
@ -1,17 +1,90 @@
|
|||
import React, { useState } from "react";
|
||||
import "./styles/PsychTestQuestion.css";
|
||||
|
||||
import { styled } from "styled-components";
|
||||
|
||||
const Question = styled.div`
|
||||
padding-top: 4px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
width: 799px;
|
||||
min-height: 151px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
margin-bottom: 34.5px;
|
||||
& h2 {
|
||||
color: #cccccc;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid #cccccc;
|
||||
border-radius: 100px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QuestionTitle = styled.p`
|
||||
margin-bottom: 80px;
|
||||
text-align: center;
|
||||
width: 638px;
|
||||
font-size: 16px;
|
||||
margin-right: 0;
|
||||
`;
|
||||
|
||||
const Answers = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 29.5px;
|
||||
& p, & span {
|
||||
font-size: 14px;
|
||||
}
|
||||
& input, & p {
|
||||
margin-right: 32px;
|
||||
transform: translateY(6px)
|
||||
}
|
||||
& label {
|
||||
transform: translateY(6px)
|
||||
}
|
||||
`;
|
||||
|
||||
const Answer = styled.label`
|
||||
& span {
|
||||
position: absolute;
|
||||
top: -17px;
|
||||
left: 3.5px
|
||||
}
|
||||
`;
|
||||
|
||||
const Id = styled.div`
|
||||
color: #cccccc;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #b2b4b2;
|
||||
border-radius: 100px;
|
||||
`;
|
||||
|
||||
|
||||
const PsychTestQuestion = (props) => {
|
||||
const answerChangeHandler = (event) => {
|
||||
props.onChangeAnswer(event.target.value, event.target.name);
|
||||
};
|
||||
return (
|
||||
<div className="question">
|
||||
<div className="question__account">{+props.account + 1}</div>
|
||||
<p className="question__title">{props.name}</p>
|
||||
<div className="question__answers">
|
||||
<Question>
|
||||
<Id>{+props.account + 1}</Id>
|
||||
<QuestionTitle>{props.name}</QuestionTitle>
|
||||
<Answers>
|
||||
<p>Не важно</p>
|
||||
<label className="question__answer">
|
||||
<Answer>
|
||||
<input
|
||||
name={props.account}
|
||||
type="radio"
|
||||
|
@ -19,8 +92,8 @@ const PsychTestQuestion = (props) => {
|
|||
onChange={answerChangeHandler}
|
||||
/>
|
||||
<span>1</span>
|
||||
</label>
|
||||
<label className="question__answer">
|
||||
</Answer>
|
||||
<Answer>
|
||||
<input
|
||||
name={props.account}
|
||||
type="radio"
|
||||
|
@ -28,8 +101,8 @@ const PsychTestQuestion = (props) => {
|
|||
onChange={answerChangeHandler}
|
||||
/>
|
||||
<span>2</span>
|
||||
</label>
|
||||
<label className="question__answer">
|
||||
</Answer>
|
||||
<Answer>
|
||||
<input
|
||||
name={props.account}
|
||||
type="radio"
|
||||
|
@ -37,8 +110,8 @@ const PsychTestQuestion = (props) => {
|
|||
onChange={answerChangeHandler}
|
||||
/>
|
||||
<span>3</span>
|
||||
</label>
|
||||
<label className="question__answer">
|
||||
</Answer>
|
||||
<Answer>
|
||||
<input
|
||||
name={props.account}
|
||||
type="radio"
|
||||
|
@ -46,8 +119,8 @@ const PsychTestQuestion = (props) => {
|
|||
onChange={answerChangeHandler}
|
||||
/>
|
||||
<span>4</span>
|
||||
</label>
|
||||
<label className="question__answer">
|
||||
</Answer>
|
||||
<Answer>
|
||||
<input
|
||||
name={props.account}
|
||||
type="radio"
|
||||
|
@ -55,10 +128,10 @@ const PsychTestQuestion = (props) => {
|
|||
onChange={answerChangeHandler}
|
||||
/>
|
||||
<span>5</span>
|
||||
</label>
|
||||
</Answer>
|
||||
<p>Очень важно</p>
|
||||
</div>
|
||||
</div>
|
||||
</Answers>
|
||||
</Question>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ const api_path = path => API_ROOT + path;
|
|||
* Api root path
|
||||
* @type {string}
|
||||
*/
|
||||
const API_ROOT = window.location.protocol + '//127.0.0.1:8000';
|
||||
// ДЛЯ ПРОДА ПОСТАВИТЬ ЭТО: '//pairent.vvsu.ru'
|
||||
const API_ROOT = window.location.protocol + '//' + window.location.host;
|
||||
// ДЛЯ ПРОДА ПОСТАВИТЬ ЭТО: '//' + window.location.host
|
||||
|
||||
/** OpenID Connect Client Config
|
||||
* @type {import('oidc-client-ts').OidcClientSettings}
|
||||
|
|
|
@ -5,13 +5,15 @@ import { Filters } from "../../API/Filters";
|
|||
|
||||
import styled from 'styled-components';
|
||||
|
||||
// swiper customization
|
||||
import './swiper.css';
|
||||
import ApartamentService from "../../API/ApartamentService";
|
||||
import { HashLoader } from 'react-spinners';
|
||||
|
||||
import AwesomeSlider from 'react-awesome-slider';
|
||||
import withAutoplay from 'react-awesome-slider/dist/autoplay';
|
||||
import 'react-awesome-slider/dist/styles.css';
|
||||
// ras customization
|
||||
import './ras.css';
|
||||
const AutoplaySlider = withAutoplay(AwesomeSlider);
|
||||
|
||||
import constants from "../../constants";
|
||||
|
||||
|
@ -376,10 +378,10 @@ export default class IndexPage extends React.Component {
|
|||
return (
|
||||
<IndexPageRoot>
|
||||
<SliderContainer>
|
||||
<AwesomeSlider bullets={false}>
|
||||
<div data-src='/images/house-s.jpg' />
|
||||
<div data-src='/images/house-s-2.jpg' />
|
||||
</AwesomeSlider>
|
||||
<AutoplaySlider play cancelOnInteraction={false} interval={8000} bullets={false}>
|
||||
<a href='https://vvsu.ithub.ru/dod' data-src='/images/OpenDoorDay.jpg' />
|
||||
<div data-src='/images/Business.jpg' />
|
||||
</AutoplaySlider>
|
||||
</SliderContainer>
|
||||
|
||||
<Title>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.awssld {
|
||||
--loader-bar-color: #0077aa !important;
|
||||
/* --loader-bar-height: 8px !important; */
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
:root {
|
||||
--swiper-scrollbar-drag-bg-color: #e1e3e1a0;
|
||||
--swiper-scrollbar-size: 5px;
|
||||
--swiper-scrollbar-bottom: 0px;
|
||||
--swiper-scrollbar-border-radius: 5px;
|
||||
|
||||
--swiper-navigation-color: white
|
||||
}
|
||||
|
||||
.swiper {
|
||||
box-shadow: 0 2px 12px #00000030;
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import PsychTestQuestion from "../PsychTestQuestion";
|
||||
import PsychTestQuestion from "../../../components/PsychTestQuestion";
|
||||
import React, { useState } from "react";
|
||||
import PsychTestAddResult from "../../API/PsychTestAddResult";
|
||||
import PsychTestAddResult from "../../../API/PsychTestAddResult";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./styles/PsychTestForm.css";
|
||||
|
||||
import { styled } from "styled-components";
|
||||
|
||||
const PsychTestForm = () => {
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const navigate = useNavigate();
|
|
@ -0,0 +1,37 @@
|
|||
import { styled } from "styled-components";
|
||||
import "./styles/PsychTestHeader.css";
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
padding-left: 40px;
|
||||
padding-top: 14px;
|
||||
align-items: center;
|
||||
margin-bottom: 55px;
|
||||
|
||||
& h1 {
|
||||
font-size: 20pt;
|
||||
}
|
||||
& button {
|
||||
color: #bababa;
|
||||
font-size: 15px;
|
||||
width: 180px;
|
||||
height: 36px;
|
||||
background-color: white;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 12px;
|
||||
margin-right: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const PsychTestHeader = () => {
|
||||
return (
|
||||
<Header>
|
||||
<button type="button" onClick={window.history.back()}>
|
||||
Вернуться назад
|
||||
</button>
|
||||
<h1>Тест на совместимость</h1>
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
export default PsychTestHeader;
|
|
@ -1,5 +1,5 @@
|
|||
import PsychTestForm from "../../components/PsychTestForm";
|
||||
import PsychTestHeader from "../../components/PsychTestHeader";
|
||||
import PsychTestForm from "./PsychTestForm";
|
||||
import PsychTestHeader from "./PsychTestHeader";
|
||||
|
||||
const PsychTest = () => {
|
||||
return (
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import styled, { keyframes } from 'styled-components';
|
||||
import SVGIcon from "../../components/UI/Icon/SVGIcon";
|
||||
import Pagination from "../../components/UI/Pagination";
|
||||
import { HashLoader } from 'react-spinners';
|
||||
|
||||
const ChatSVG = () => {
|
||||
return <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9.06 9.06 0 0 0 8 15z"/></svg>;
|
||||
|
@ -101,7 +102,6 @@ const UserList = styled.div`
|
|||
margin: 32px auto;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
|
||||
|
@ -214,25 +214,32 @@ const RightBadge = styled(LeftBadge)`
|
|||
}
|
||||
`;
|
||||
|
||||
class Users extends React.Component {
|
||||
const LoadingText = styled.h2`
|
||||
text-align: center;
|
||||
line-height: 3.5em;
|
||||
margin: 0;
|
||||
padding: 100px 0;
|
||||
|
||||
& span {
|
||||
display: inline;
|
||||
margin: 0px auto
|
||||
}
|
||||
`;
|
||||
|
||||
class UserDisplay extends React.Component {
|
||||
|
||||
/** @type {{ value: import('../../API/User').User[] }} */
|
||||
props;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UserList>
|
||||
|
||||
<h2 style={{textAlign: 'center', lineHeight: '11pt', marginBottom: 32}}>
|
||||
Выбери соседа
|
||||
<br/>
|
||||
<br/>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 500 }}>
|
||||
Не забывай, с этим человеком<br/>
|
||||
придется жить бок-о-бок!
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div>123
|
||||
{
|
||||
[...Array(11)].map((_, i) => {
|
||||
if (i == 5) return <br/>;
|
||||
|
||||
this.props.value.map(x => {
|
||||
return (
|
||||
<UserCard>
|
||||
<SVGIcon src='/images/icons/user.svg' width='100' height='100' />
|
||||
|
@ -254,9 +261,50 @@ class Users extends React.Component {
|
|||
</ContactButton>
|
||||
|
||||
</UserCard>
|
||||
);
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Users extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: [{}],
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UserList>
|
||||
|
||||
<h2 style={{textAlign: 'center', lineHeight: '11pt', marginBottom: 32}}>
|
||||
Выбери соседа
|
||||
<br/>
|
||||
<br/>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 500 }}>
|
||||
Не забывай, с этим человеком<br/>
|
||||
придется жить бок-о-бок!
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{
|
||||
this.state.loading ?
|
||||
<UserDisplay value={this.state.data} /> :
|
||||
<LoadingText>
|
||||
Пожалуйста подождите, идет загрузка данных<br/>
|
||||
<HashLoader color='#0077aa' />
|
||||
</LoadingText>
|
||||
}
|
||||
|
||||
</UserList>
|
||||
)
|
||||
|
|
|
@ -4,6 +4,5 @@ import react from '@vitejs/plugin-react'
|
|||
export default {
|
||||
plugins: [
|
||||
react()
|
||||
],
|
||||
|
||||
],
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Pairent</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script type="module" crossorigin src="/assets/index-22e3f5a9.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-b130f2c5.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue