From d0550acff9ac19f68e640d738e617148e6b9be16 Mon Sep 17 00:00:00 2001 From: albivkt Date: Wed, 6 Aug 2025 05:09:12 +0300 Subject: [PATCH] Implement custom dark theme for the app and enhance login/register screens with animations. Update dependencies and fix package versions in package.json and package-lock.json. --- frontend/App.tsx | 35 ++- frontend/package-lock.json | 297 ++++++++++++++---- frontend/package.json | 15 +- frontend/src/screens/LoginScreen.tsx | 306 ++++++++++++++++--- frontend/src/screens/RegisterScreen.tsx | 387 ++++++++++++++++++++---- 5 files changed, 869 insertions(+), 171 deletions(-) diff --git a/frontend/App.tsx b/frontend/App.tsx index ebc92a8..3b28f26 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -1,20 +1,49 @@ import React from 'react'; import { StatusBar } from 'expo-status-bar'; import { ApolloProvider } from '@apollo/client'; -import { Provider as PaperProvider } from 'react-native-paper'; +import { Provider as PaperProvider, MD3DarkTheme, configureFonts } from 'react-native-paper'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { apolloClient } from './src/services/apollo-client'; import { AuthProvider } from './src/contexts/AuthContext'; import { AppNavigator } from './src/navigation/AppNavigator'; +// Кастомная темная тема в черно-фиолетовых тонах +const theme = { + ...MD3DarkTheme, + colors: { + ...MD3DarkTheme.colors, + primary: '#9333ea', + secondary: '#a855f7', + tertiary: '#7c3aed', + background: '#0a0a0f', + surface: '#1a1a2e', + surfaceVariant: '#2d2d42', + onSurface: '#ffffff', + onSurfaceVariant: '#e5e5e7', + onPrimary: '#ffffff', + elevation: { + level0: 'transparent', + level1: '#1a1a2e', + level2: '#2d2d42', + level3: '#3d3d56', + level4: '#4d4d6a', + level5: '#5d5d7e', + }, + outline: '#7c3aed', + outlineVariant: '#6d28d9', + error: '#ef4444', + }, + roundness: 12, +}; + export default function App() { return ( - + - + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a32194d..fe50e94 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "dependencies": { "@apollo/client": "^3.13.9", - "@react-native-async-storage/async-storage": "^2.2.0", + "@expo/metro-runtime": "~5.0.4", + "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/bottom-tabs": "^7.4.5", "@react-navigation/native": "^7.1.17", "@react-navigation/native-stack": "^7.3.24", @@ -18,14 +19,16 @@ "expo-status-bar": "~2.2.3", "graphql": "^16.11.0", "react": "19.0.0", + "react-dom": "19.0.0", "react-native": "0.79.5", - "react-native-gesture-handler": "^2.27.2", + "react-native-gesture-handler": "~2.24.0", "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-paper": "^5.14.5", - "react-native-reanimated": "^4.0.1", - "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "^4.13.1", - "react-native-vector-icons": "^10.3.0" + "react-native-reanimated": "~3.17.4", + "react-native-safe-area-context": "5.4.0", + "react-native-screens": "~4.11.1", + "react-native-vector-icons": "^10.3.0", + "react-native-web": "^0.20.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -1406,7 +1409,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1917,6 +1919,15 @@ "resolve-from": "^5.0.0" } }, + "node_modules/@expo/metro-runtime": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-5.0.4.tgz", + "integrity": "sha512-r694MeO+7Vi8IwOsDIDzH/Q5RPMt1kUDYbiTJwnO15nIqiDwlE8HU55UlRhffKZy6s5FmxQsZ8HA+T8DqUW8cQ==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/@expo/osascript": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.5.tgz", @@ -2380,9 +2391,9 @@ } }, "node_modules/@react-native-async-storage/async-storage": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", - "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", "license": "MIT", "dependencies": { "merge-options": "^3.0.4" @@ -3945,6 +3956,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3968,6 +3988,15 @@ "node": ">=8" } }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -4410,6 +4439,36 @@ "bser": "2.1.1" } }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4748,6 +4807,12 @@ "node": ">= 14" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4846,6 +4911,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6192,6 +6266,26 @@ "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -6667,6 +6761,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -6870,6 +6970,18 @@ } } }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/react-freeze": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", @@ -6958,9 +7070,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.27.2", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.2.tgz", - "integrity": "sha512-+kNaY2m7uQu5+5ls8os6z92DTk9expsEAYsaPv30n08mrqX2r64G8iVGDwNWzZcId54+P7RlDnhyszTql0sQ0w==", + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.24.0.tgz", + "integrity": "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", @@ -7050,37 +7162,44 @@ "license": "MIT" }, "node_modules/react-native-reanimated": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.0.1.tgz", - "integrity": "sha512-SZmIpxVd1yijV1MA8KB9S9TUj6JpdU4THjVB0WCkfV9p6F8oR3YxO4e+GRKbNci3mODp7plW095LhjaCB9bqZQ==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz", + "integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==", "license": "MIT", "dependencies": { - "react-native-is-edge-to-edge": "^1.2.1", - "semver": "7.7.2" + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-class-properties": "^7.0.0-0", + "@babel/plugin-transform-classes": "^7.0.0-0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-transform-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", + "@babel/plugin-transform-unicode-regex": "^7.0.0-0", + "@babel/preset-typescript": "^7.16.7", + "convert-source-map": "^2.0.0", + "invariant": "^2.2.4", + "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", - "react-native": "*", - "react-native-worklets": ">=0.3.0" + "react-native": "*" } }, - "node_modules/react-native-reanimated/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz", + "integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, "node_modules/react-native-safe-area-context": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.2.tgz", - "integrity": "sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", + "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==", "license": "MIT", "peerDependencies": { "react": "*", @@ -7088,13 +7207,13 @@ } }, "node_modules/react-native-screens": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.13.1.tgz", - "integrity": "sha512-EESsMAtyzYcL3gpAI2NKKiIo+Ew0fnX4P4b3Zy/+MTc6SJIo3foJbZwdIWd/SUBswOf7IYCvWBppg+D8tbwnsw==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz", + "integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==", "license": "MIT", "dependencies": { "react-freeze": "^1.0.0", - "react-native-is-edge-to-edge": "^1.2.1", + "react-native-is-edge-to-edge": "^1.1.7", "warn-once": "^0.1.0" }, "peerDependencies": { @@ -7189,30 +7308,38 @@ "node": ">=10" } }, - "node_modules/react-native-worklets": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.4.1.tgz", - "integrity": "sha512-QXAMZ8jz0sLEoNrc3ej050z6Sd+UJ/Gef4SACeMuoLRinwHIy4uel7XtMPJZMqKhFerkwXZ7Ips5vIjnNyPDBA==", + "node_modules/react-native-web": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz", + "integrity": "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg==", "license": "MIT", - "peer": true, "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@babel/plugin-transform-unicode-regex": "^7.0.0-0", - "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^2.0.0" + "@babel/runtime": "^7.18.6", + "@react-native/normalize-colors": "^0.74.1", + "fbjs": "^3.0.4", + "inline-style-prefixer": "^7.0.1", + "memoize-one": "^6.0.0", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "styleq": "^0.1.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", - "react": "*", - "react-native": "*" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/react-native-web/node_modules/@react-native/normalize-colors": { + "version": "0.74.89", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz", + "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==", + "license": "MIT" + }, + "node_modules/react-native-web/node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.5.tgz", @@ -7781,6 +7908,12 @@ "node": ">= 0.8" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8117,6 +8250,12 @@ "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", "license": "MIT" }, + "node_modules/styleq": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", + "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -8395,6 +8534,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -8451,6 +8596,32 @@ "node": ">=14.17" } }, + "node_modules/ua-parser-js": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/undici": { "version": "6.21.3", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", @@ -8656,6 +8827,16 @@ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -8670,6 +8851,12 @@ "node": ">=10" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 641a4e1..7738b04 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@apollo/client": "^3.13.9", - "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/bottom-tabs": "^7.4.5", "@react-navigation/native": "^7.1.17", "@react-navigation/native-stack": "^7.3.24", @@ -20,13 +20,16 @@ "graphql": "^16.11.0", "react": "19.0.0", "react-native": "0.79.5", - "react-native-gesture-handler": "^2.27.2", + "react-native-gesture-handler": "~2.24.0", "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-paper": "^5.14.5", - "react-native-reanimated": "^4.0.1", - "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "^4.13.1", - "react-native-vector-icons": "^10.3.0" + "react-native-reanimated": "~3.17.4", + "react-native-safe-area-context": "5.4.0", + "react-native-screens": "~4.11.1", + "react-native-vector-icons": "^10.3.0", + "react-dom": "19.0.0", + "react-native-web": "^0.20.0", + "@expo/metro-runtime": "~5.0.4" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/frontend/src/screens/LoginScreen.tsx b/frontend/src/screens/LoginScreen.tsx index cf54dea..3e85c1c 100644 --- a/frontend/src/screens/LoginScreen.tsx +++ b/frontend/src/screens/LoginScreen.tsx @@ -1,9 +1,23 @@ -import React, { useState } from 'react'; -import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native'; import { TextInput, Button, Text, Headline, HelperText } from 'react-native-paper'; import { useMutation } from '@apollo/client'; import { LOGIN } from '../graphql/mutations'; import { useAuth } from '../contexts/AuthContext'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + withSpring, + withDelay, + withSequence, + withRepeat, + interpolate, + Easing, +} from 'react-native-reanimated'; + +const { width: screenWidth } = Dimensions.get('window'); +const AnimatedView = Animated.View; export const LoginScreen = ({ navigation }: any) => { const [username, setUsername] = useState(''); @@ -11,6 +25,32 @@ export const LoginScreen = ({ navigation }: any) => { const [showPassword, setShowPassword] = useState(false); const { login } = useAuth(); + // Анимации + const translateY = useSharedValue(50); + const opacity = useSharedValue(0); + const scale = useSharedValue(0.9); + const glowAnimation = useSharedValue(0); + const buttonScale = useSharedValue(1); + const inputFocusAnimation1 = useSharedValue(0); + const inputFocusAnimation2 = useSharedValue(0); + + useEffect(() => { + // Анимация появления + translateY.value = withSpring(0, { damping: 15, stiffness: 100 }); + opacity.value = withTiming(1, { duration: 800 }); + scale.value = withSpring(1, { damping: 15, stiffness: 100 }); + + // Пульсирующее свечение + glowAnimation.value = withRepeat( + withSequence( + withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }), + withTiming(0, { duration: 2000, easing: Easing.inOut(Easing.ease) }) + ), + -1, + false + ); + }, []); + const [loginMutation, { loading, error }] = useMutation(LOGIN, { onCompleted: async (data) => { await login(data.login.access_token, data.login.user); @@ -25,66 +65,173 @@ export const LoginScreen = ({ navigation }: any) => { loginMutation({ variables: { username, password } }); }; + // Анимированные стили + const containerAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { translateY: translateY.value }, + { scale: scale.value } + ], + opacity: opacity.value, + }; + }); + + const glowContainerStyle = useAnimatedStyle(() => { + const glowOpacity = interpolate(glowAnimation.value, [0, 1], [0.3, 0.8]); + const shadowRadius = interpolate(glowAnimation.value, [0, 1], [10, 30]); + + return { + shadowOpacity: glowOpacity, + shadowRadius: shadowRadius, + elevation: interpolate(glowAnimation.value, [0, 1], [5, 15]), + }; + }); + + const buttonAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [{ scale: buttonScale.value }], + }; + }); + + const createInputAnimatedStyle = (focusAnimation: any) => { + return useAnimatedStyle(() => { + const borderWidth = interpolate(focusAnimation.value, [0, 1], [1, 2]); + const shadowOpacity = interpolate(focusAnimation.value, [0, 1], [0, 0.6]); + + return { + borderWidth: borderWidth, + shadowOpacity: shadowOpacity, + elevation: interpolate(focusAnimation.value, [0, 1], [2, 8]), + }; + }); + }; + + const inputStyle1 = createInputAnimatedStyle(inputFocusAnimation1); + const inputStyle2 = createInputAnimatedStyle(inputFocusAnimation2); + + const handleButtonPressIn = () => { + buttonScale.value = withSpring(0.95); + }; + + const handleButtonPressOut = () => { + buttonScale.value = withSpring(1); + }; + return ( - - Вход в Prism + + + Вход в Prism + Добро пожаловать обратно + - + + { + inputFocusAnimation1.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation1.value = withSpring(0); + }} + /> + - setShowPassword(!showPassword)} - /> - } - /> + + setShowPassword(!showPassword)} + color="#a855f7" + /> + } + onFocus={() => { + inputFocusAnimation2.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation2.value = withSpring(0); + }} + /> + {error && ( - + {error.message} )} - + + + - + ); @@ -93,11 +240,12 @@ export const LoginScreen = ({ navigation }: any) => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#f5f5f5', + backgroundColor: '#0a0a0f', }, scrollContent: { flexGrow: 1, justifyContent: 'center', + paddingVertical: 40, }, content: { padding: 20, @@ -105,18 +253,90 @@ const styles = StyleSheet.create({ width: '100%', alignSelf: 'center', }, + glowContainer: { + marginBottom: 40, + padding: 20, + borderRadius: 20, + backgroundColor: '#1a1a2e', + // iOS тени + shadowColor: '#9333ea', + shadowOffset: { + width: 0, + height: 0, + }, + shadowOpacity: 0.5, + shadowRadius: 20, + // Android тень + elevation: 10, + }, title: { textAlign: 'center', - marginBottom: 30, + marginBottom: 10, + fontSize: 32, + fontWeight: 'bold', + color: '#ffffff', + textShadowColor: '#9333ea', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 10, + }, + subtitle: { + textAlign: 'center', + fontSize: 16, + color: '#a855f7', + fontStyle: 'italic', }, input: { - marginBottom: 15, + marginBottom: 20, + backgroundColor: '#1a1a2e', + borderRadius: 12, + // iOS тени + shadowColor: '#7c3aed', + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.3, + shadowRadius: 6, + // Android тень + elevation: 5, }, button: { - marginTop: 10, + marginTop: 20, marginBottom: 10, + borderRadius: 12, + backgroundColor: '#9333ea', + // iOS тени для кнопки + shadowColor: '#9333ea', + shadowOffset: { + width: 0, + height: 8, + }, + shadowOpacity: 0.6, + shadowRadius: 15, + // Android тень + elevation: 10, + }, + buttonContent: { + paddingVertical: 8, + }, + buttonLabel: { + fontSize: 18, + fontWeight: 'bold', + letterSpacing: 1, }, linkButton: { marginTop: 10, }, + linkButtonLabel: { + fontSize: 16, + color: '#a855f7', + }, + errorText: { + color: '#ef4444', + textAlign: 'center', + marginBottom: 10, + textShadowColor: '#ef4444', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 5, + }, }); \ No newline at end of file diff --git a/frontend/src/screens/RegisterScreen.tsx b/frontend/src/screens/RegisterScreen.tsx index a46c176..7b14f7d 100644 --- a/frontend/src/screens/RegisterScreen.tsx +++ b/frontend/src/screens/RegisterScreen.tsx @@ -1,9 +1,23 @@ -import React, { useState } from 'react'; -import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native'; import { TextInput, Button, Text, Headline, HelperText } from 'react-native-paper'; import { useMutation } from '@apollo/client'; import { REGISTER } from '../graphql/mutations'; import { useAuth } from '../contexts/AuthContext'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + withSpring, + withDelay, + withSequence, + withRepeat, + interpolate, + Easing, +} from 'react-native-reanimated'; + +const { width: screenWidth } = Dimensions.get('window'); +const AnimatedView = Animated.View; export const RegisterScreen = ({ navigation }: any) => { const [username, setUsername] = useState(''); @@ -13,6 +27,34 @@ export const RegisterScreen = ({ navigation }: any) => { const [showPassword, setShowPassword] = useState(false); const { login } = useAuth(); + // Анимации + const translateY = useSharedValue(50); + const opacity = useSharedValue(0); + const scale = useSharedValue(0.9); + const glowAnimation = useSharedValue(0); + const buttonScale = useSharedValue(1); + const inputFocusAnimation1 = useSharedValue(0); + const inputFocusAnimation2 = useSharedValue(0); + const inputFocusAnimation3 = useSharedValue(0); + const inputFocusAnimation4 = useSharedValue(0); + + useEffect(() => { + // Анимация появления + translateY.value = withSpring(0, { damping: 15, stiffness: 100 }); + opacity.value = withTiming(1, { duration: 800 }); + scale.value = withSpring(1, { damping: 15, stiffness: 100 }); + + // Пульсирующее свечение + glowAnimation.value = withRepeat( + withSequence( + withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }), + withTiming(0, { duration: 2000, easing: Easing.inOut(Easing.ease) }) + ), + -1, + false + ); + }, []); + const [registerMutation, { loading, error }] = useMutation(REGISTER, { onCompleted: async (data) => { await login(data.register.access_token, data.register.user); @@ -29,94 +71,238 @@ export const RegisterScreen = ({ navigation }: any) => { const passwordsMatch = password === confirmPassword || confirmPassword === ''; + // Анимированные стили + const containerAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { translateY: translateY.value }, + { scale: scale.value } + ], + opacity: opacity.value, + }; + }); + + const glowContainerStyle = useAnimatedStyle(() => { + const glowOpacity = interpolate(glowAnimation.value, [0, 1], [0.3, 0.8]); + const shadowRadius = interpolate(glowAnimation.value, [0, 1], [10, 30]); + + return { + shadowOpacity: glowOpacity, + shadowRadius: shadowRadius, + elevation: interpolate(glowAnimation.value, [0, 1], [5, 15]), + }; + }); + + const buttonAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [{ scale: buttonScale.value }], + }; + }); + + const createInputAnimatedStyle = (focusAnimation: any) => { + return useAnimatedStyle(() => { + const borderWidth = interpolate(focusAnimation.value, [0, 1], [1, 2]); + const shadowOpacity = interpolate(focusAnimation.value, [0, 1], [0, 0.6]); + + return { + borderWidth: borderWidth, + shadowOpacity: shadowOpacity, + elevation: interpolate(focusAnimation.value, [0, 1], [2, 8]), + }; + }); + }; + + const inputStyle1 = createInputAnimatedStyle(inputFocusAnimation1); + const inputStyle2 = createInputAnimatedStyle(inputFocusAnimation2); + const inputStyle3 = createInputAnimatedStyle(inputFocusAnimation3); + const inputStyle4 = createInputAnimatedStyle(inputFocusAnimation4); + + const handleButtonPressIn = () => { + buttonScale.value = withSpring(0.95); + }; + + const handleButtonPressOut = () => { + buttonScale.value = withSpring(1); + }; + return ( - - Регистрация в Prism + + + Регистрация в Prism + Присоединяйтесь к будущему + - + + { + inputFocusAnimation1.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation1.value = withSpring(0); + }} + /> + - + + { + inputFocusAnimation2.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation2.value = withSpring(0); + }} + /> + - setShowPassword(!showPassword)} - /> - } - /> + + setShowPassword(!showPassword)} + color="#a855f7" + /> + } + onFocus={() => { + inputFocusAnimation3.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation3.value = withSpring(0); + }} + /> + - + + { + inputFocusAnimation4.value = withSpring(1); + }} + onBlur={() => { + inputFocusAnimation4.value = withSpring(0); + }} + /> + {!passwordsMatch && ( - + Пароли не совпадают )} {error && ( - + {error.message} )} - + + + - + ); @@ -125,11 +311,12 @@ export const RegisterScreen = ({ navigation }: any) => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#f5f5f5', + backgroundColor: '#0a0a0f', }, scrollContent: { flexGrow: 1, justifyContent: 'center', + paddingVertical: 40, }, content: { padding: 20, @@ -137,18 +324,90 @@ const styles = StyleSheet.create({ width: '100%', alignSelf: 'center', }, + glowContainer: { + marginBottom: 40, + padding: 20, + borderRadius: 20, + backgroundColor: '#1a1a2e', + // iOS тени + shadowColor: '#9333ea', + shadowOffset: { + width: 0, + height: 0, + }, + shadowOpacity: 0.5, + shadowRadius: 20, + // Android тень + elevation: 10, + }, title: { textAlign: 'center', - marginBottom: 30, + marginBottom: 10, + fontSize: 32, + fontWeight: 'bold', + color: '#ffffff', + textShadowColor: '#9333ea', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 10, + }, + subtitle: { + textAlign: 'center', + fontSize: 16, + color: '#a855f7', + fontStyle: 'italic', }, input: { - marginBottom: 15, + marginBottom: 20, + backgroundColor: '#1a1a2e', + borderRadius: 12, + // iOS тени + shadowColor: '#7c3aed', + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.3, + shadowRadius: 6, + // Android тень + elevation: 5, }, button: { - marginTop: 10, + marginTop: 20, marginBottom: 10, + borderRadius: 12, + backgroundColor: '#9333ea', + // iOS тени для кнопки + shadowColor: '#9333ea', + shadowOffset: { + width: 0, + height: 8, + }, + shadowOpacity: 0.6, + shadowRadius: 15, + // Android тень + elevation: 10, + }, + buttonContent: { + paddingVertical: 8, + }, + buttonLabel: { + fontSize: 18, + fontWeight: 'bold', + letterSpacing: 1, }, linkButton: { marginTop: 10, }, + linkButtonLabel: { + fontSize: 16, + color: '#a855f7', + }, + errorText: { + color: '#ef4444', + textAlign: 'center', + marginBottom: 10, + textShadowColor: '#ef4444', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 5, + }, }); \ No newline at end of file