diff --git a/backend/project/api/teams/migrations/0001_initial.py b/backend/project/api/teams/migrations/0001_initial.py new file mode 100644 index 0000000..47871cd --- /dev/null +++ b/backend/project/api/teams/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2.11 on 2024-03-31 19:06 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('users', '0002_rename_technologies_user_skills'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Vacancy', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='название вакансии')), + ('start_date', models.DateField(blank=True, null=True, verbose_name='дата начала диапазона возраста участников')), + ('end_date', models.DateField(blank=True, null=True, verbose_name='дата конец диапазона возраста участников')), + ('skills', models.ManyToManyField(blank=True, to='users.skill', verbose_name='Технологии')), + ('specialization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='users.specialization', verbose_name='специализация')), + ], + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(verbose_name='описание команды')), + ('name', models.CharField(max_length=255, verbose_name='название команды')), + ('avatar', models.ImageField(blank=True, upload_to='teams_avatars', verbose_name='аватарка')), + ('count_of_members', models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxLengthValidator(5)], verbose_name='количество участников')), + ('country', models.CharField(blank=True, max_length=255, verbose_name='страна')), + ('city', models.CharField(blank=True, max_length=255, verbose_name='город')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams', to=settings.AUTH_USER_MODEL)), + ('members', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='участники')), + ('vacancies', models.ManyToManyField(to='teams.vacancy', verbose_name='вакансии')), + ], + ), + ] diff --git a/backend/project/api/teams/serializer.py b/backend/project/api/teams/serializers.py similarity index 100% rename from backend/project/api/teams/serializer.py rename to backend/project/api/teams/serializers.py diff --git a/backend/project/config/settings.py b/backend/project/config/settings.py index 346389d..e0255e4 100755 --- a/backend/project/config/settings.py +++ b/backend/project/config/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ # Third-party apps "rest_framework", "rest_framework_simplejwt", + "corsheaders", "drf_yasg", # Developed apps "api.ping.apps.PingConfig", @@ -58,6 +59,8 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", ] ROOT_URLCONF = "config.urls" diff --git a/backend/project/notifications/migrations/0002_alter_notification_content.py b/backend/project/notifications/migrations/0002_alter_notification_content.py new file mode 100644 index 0000000..0bdbb42 --- /dev/null +++ b/backend/project/notifications/migrations/0002_alter_notification_content.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-03-31 19:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='content', + field=models.TextField(verbose_name='содержание'), + ), + ] diff --git a/backend/requirements/lint.txt b/backend/requirements/lint.txt new file mode 100644 index 0000000..40b7f22 --- /dev/null +++ b/backend/requirements/lint.txt @@ -0,0 +1,2 @@ +sort-requirements +ruff diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index 2e3883a..27510b8 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -8,5 +8,6 @@ djangorestframework-simplejwt==5.3.1 django-filter==24.2 Pillow==10.2.0 drf-yasg==1.21.7 +django-cors-headers setuptools bcrypt==4.1.2 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 74ccdf7..6f32b45 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,10 +1,12 @@ { + "name": "skill-hub", "name": "skill-hub", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "skill-hub", "name": "skill-hub", "version": "0.0.0", "dependencies": { @@ -19,6 +21,8 @@ "@radix-ui/react-tabs": "^1.0.4", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.19", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.19", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cn-decorator": "^2.1.0", @@ -26,8 +30,10 @@ "eslint-plugin-react": "^7.34.1", "i18next": "^23.10.1", "less": "^4.2.0", + "less": "^4.2.0", "lucide-react": "^0.363.0", "postcss": "^8.4.38", + "postcss": "^8.4.38", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.2", @@ -36,9 +42,12 @@ "react-router-dom": "^6.22.3", "tailwind-merge": "^2.2.2", "tailwindcss": "^3.4.3", + "tailwindcss": "^3.4.3", "tailwindcss-animate": "^1.0.7", "typescript": "^5.2.2", "vite": "^5.2.0", + "typescript": "^5.2.2", + "vite": "^5.2.0", "zod": "^3.22.4" }, "devDependencies": { @@ -54,6 +63,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.2.5" + "prettier": "^3.2.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1792,6 +1802,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/types": { "version": "0.1.6", @@ -1805,6 +1816,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1824,6 +1836,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "devOptional": true, + "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -2625,6 +2638,11 @@ "resolved": "https://registry.npmjs.org/cross/-/cross-1.0.0.tgz", "integrity": "sha512-p6hXbCnjuIB4bhKWFeztQd7VwffgQP9zOBzUoiA8Lvi01RzQY0e7PbPFU/uqVPTM2stY7uCpVck1UTPpxhinMQ==" }, + "node_modules/cross": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cross/-/cross-1.0.0.tgz", + "integrity": "sha512-p6hXbCnjuIB4bhKWFeztQd7VwffgQP9zOBzUoiA8Lvi01RzQY0e7PbPFU/uqVPTM2stY7uCpVck1UTPpxhinMQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2811,6 +2829,7 @@ "version": "1.4.721", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==" + "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5020,6 +5039,7 @@ "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -6586,6 +6606,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "devOptional": true + "devOptional": true }, "node_modules/update-browserslist-db": { "version": "1.0.13", diff --git a/frontend/src/components/app/APIurl.ts b/frontend/src/components/app/APIurl.ts new file mode 100644 index 0000000..f9dc4f1 --- /dev/null +++ b/frontend/src/components/app/APIurl.ts @@ -0,0 +1,6 @@ +// export const API_BASE = "http://158.160.56.239:8080/api/" +// export const API_BASE = "http://212.22.79.188:9090/api/" //2 сервер +export const API_BASE = "http://localhost:8080/api/" //3 сервер + +export const API_REG = "registration/" +export const API_CREATE_TOKEN = "token/" diff --git a/frontend/src/components/features/SearchBar/SearchBar.module.less b/frontend/src/components/features/SearchBar/SearchBar.module.less index 3c56a78..0fbdfa4 100644 --- a/frontend/src/components/features/SearchBar/SearchBar.module.less +++ b/frontend/src/components/features/SearchBar/SearchBar.module.less @@ -13,7 +13,6 @@ font-weight: 900; font-size: 16px; line-height: 100%; -color: #000; } .input-search{ border: 0; diff --git a/frontend/src/components/features/UserProfile/UserProfile.module.less b/frontend/src/components/features/UserProfile/UserProfile.module.less new file mode 100644 index 0000000..8e5a755 --- /dev/null +++ b/frontend/src/components/features/UserProfile/UserProfile.module.less @@ -0,0 +1,39 @@ +@icon-size: 256px; + +.user-icon{ + margin: 20px; + + width: @icon-size; + height: @icon-size; + + border-radius: 200px; + + background-color: #222222; + border: 2px #3c3c3c solid; +} +.username{ + + color: #828282; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-weight: lighter; + font-weight: 200; + font-size: 24px; +} +.card{ + margin: 20px; + padding: 10px; + width: calc(100% - 20px); + +} +.info-line{ + display: flex; +} + +.h1{ + font-size: 36px; +} +.p{ + font-size: 16px; + color: #d3d3d3; +} + diff --git a/frontend/src/components/features/UserProfile/UserProfile.tsx b/frontend/src/components/features/UserProfile/UserProfile.tsx new file mode 100644 index 0000000..5a92879 --- /dev/null +++ b/frontend/src/components/features/UserProfile/UserProfile.tsx @@ -0,0 +1,41 @@ + +import { Separator } from "../../shared/ui/separator"; +import { Card } from "../../shared/ui/card"; +import less from "./UserProfile.module.less" +import { PanelRightDashedIcon, Pointer, PointerOff } from "lucide-react"; + +const UserProfile = () => { + return ( +
+
+
user-icon
+

username

+ +
+ + +

Город

Москва

+
+ + +

Город

Москва

+
+ + + +

telegram

@wolf

+
+ +
+
+
+ +

Имя Фамилия

+ +

+ +
+
+ ) +} +export default UserProfile; \ No newline at end of file diff --git a/frontend/src/components/pages/MyTeams/MyTeams.tsx b/frontend/src/components/pages/MyTeams/MyTeams.tsx index 90e37cf..0a7e52f 100644 --- a/frontend/src/components/pages/MyTeams/MyTeams.tsx +++ b/frontend/src/components/pages/MyTeams/MyTeams.tsx @@ -1,9 +1,16 @@ +import TeamCard from "../../widgets/TeamCard/TeamCard"; +import UserProfile from "../../features/UserProfile/UserProfile"; import less from "./MyTeams.module.less" +import CreateTeam from "../../widgets/CreateTeam/CreateTeam"; const MyTeams = () => { return ( -

My teams

+ <> + + + + ) } export default MyTeams; \ No newline at end of file diff --git a/frontend/src/components/shared/ui/sheet.tsx b/frontend/src/components/shared/ui/sheet.tsx new file mode 100644 index 0000000..49e8d14 --- /dev/null +++ b/frontend/src/components/shared/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "../../../../lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/frontend/src/components/shared/ui/textarea.tsx b/frontend/src/components/shared/ui/textarea.tsx new file mode 100644 index 0000000..0236ee7 --- /dev/null +++ b/frontend/src/components/shared/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "../../../../lib/utils" + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( + + + + + + + + + ) +} +export default CreateTeam; \ No newline at end of file diff --git a/frontend/src/components/widgets/Header/AuthAPI.ts b/frontend/src/components/widgets/Header/AuthAPI.ts index b53373a..4752a05 100644 --- a/frontend/src/components/widgets/Header/AuthAPI.ts +++ b/frontend/src/components/widgets/Header/AuthAPI.ts @@ -1,15 +1,67 @@ import { FormEvent } from "react"; +import { API_BASE, API_CREATE_TOKEN, API_REG } from "../../app/APIurl"; - +//логин export const submitLogin = (e: FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const formProps = Object.fromEntries(formData); console.log(formProps) + + + fetch(`${API_BASE}${API_CREATE_TOKEN}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(formProps) + }) + .then(response => { + console.log(response.status); + if (response.ok) { + console.log('Создан:', response.headers.get('Location')); + return response.json(); + } else { + throw new Error('Код ошибки: ' + response.status); + } + }) + .then(data => { + console.log('Успешно:', data); + }) + .catch(error => { + console.error('Возникла ошибка с логином4:', error); + }); } + +//регистрация export const submitRegister = (e: FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const formProps = Object.fromEntries(formData); - console.log(formProps) -} \ No newline at end of file + + + fetch(`${API_BASE}${API_REG}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(formProps) + }) + .then(response => { + console.log(response.status); + if (response.ok) { + console.log('Создан:', response.headers.get('Location')); + return response.json(); + } else { + throw new Error('Код ошибки: ' + response.status); + } + }) + .then(data => { + console.log('Успешно:', data); + }) + .catch(error => { + console.error('Возникла ошибка с регой:', error); + }); +} + + \ No newline at end of file diff --git a/frontend/src/components/widgets/Header/AuthForm.tsx b/frontend/src/components/widgets/Header/AuthForm.tsx index 849c355..aa0c4e8 100644 --- a/frontend/src/components/widgets/Header/AuthForm.tsx +++ b/frontend/src/components/widgets/Header/AuthForm.tsx @@ -1,42 +1,65 @@ -import { DialogContent, DialogTitle, DialogDescription } from "../../shared/ui/dialog"; +import { DialogContent, DialogTitle, DialogDescription, Dialog, DialogTrigger } from "../../shared/ui/dialog"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../shared/ui/tabs"; import { t } from "i18next"; import { Button } from "../../shared/ui/button"; import { DialogHeader } from "../../shared/ui/dialog"; import { Input } from "../../shared/ui/input"; import { submitLogin, submitRegister } from "./AuthAPI"; +import { Textarea } from "../../shared/ui/textarea"; const AuthForm = () => { return ( - - - {t("entrance")} - - - {t("login")} - {t("registration")} - - -
- - - -
-
- -
- - - - - -
-
-
-
-
+ + + {t("entrance")} + + + {t("login")} + {t("registration")} + + +
+ + + +
+
+ +
+ + + + + + + {t("buttonRegInSystemStep1")} + + + + Еще немного! + + + + + + + + +