From ac43b806f7e141ed655885f3e496c107d38305f7 Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Sun, 25 Aug 2024 01:33:45 +0530 Subject: [PATCH 001/127] Add verification for oldPassword --- server/src/controllers/auth/updatePassword.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/controllers/auth/updatePassword.ts b/server/src/controllers/auth/updatePassword.ts index 5496835ea..69c413e2d 100644 --- a/server/src/controllers/auth/updatePassword.ts +++ b/server/src/controllers/auth/updatePassword.ts @@ -25,13 +25,13 @@ export function updatePasswordRoute() { return protectedProcedure .input(validator.updatePassword) .mutation(async (opts) => { - const { email, password } = opts.input; + const { email, oldPassword, password } = opts.rawInput; const { env }: any = opts.ctx; const JWT_SECRET = env.JWT_SECRET; const userClass = new User(); - const user = await userClass.findByCredentials(email, password); + const user = await userClass.findByCredentials(email, oldPassword); if (!user) { - throw new Error('Password is not correct'); + throw new Error('Old password is incorrect'); } const hashedPassword = await hashPassword(JWT_SECRET, password); const currentUser = await findUserAndUpdate( From 5234089630e447c05b8c57d635e01f0a97ac548f Mon Sep 17 00:00:00 2001 From: Tadjaur Date: Sun, 25 Aug 2024 06:28:54 +0100 Subject: [PATCH 002/127] Create a map package and remove unnecessary file --- apps/vite/src/routeTree.gen.ts | 177 ++++++- packages/app/components/destination/index.tsx | 14 +- .../components/map/MapContainer.native.tsx | 46 -- packages/app/components/map/MapContainer.tsx | 37 -- packages/app/components/map/index.tsx | 2 - packages/app/components/trip/TripCard.tsx | 7 +- .../components/trip/TripCards/TripMapCard.tsx | 8 +- packages/app/hooks/map/index.ts | 2 - packages/app/package.json | 1 + packages/app/screens/maps/index.tsx | 4 +- packages/app/utils/isObjectEmpty.ts | 2 +- packages/config/src/index.js | 5 +- packages/map/.eslintrc.js | 3 + packages/map/CHANGELOG.md | 7 + packages/map/README.md | 33 ++ packages/map/package.json | 30 ++ .../components/map => map/src}/Map.native.tsx | 19 +- .../{app/components/map => map/src}/Map.tsx | 44 +- .../map => map/src}/MapButtonsOverlay.tsx | 220 +++++---- .../components/map => map/src}/MapPreview.tsx | 5 +- packages/map/src/hooks/useCustomStyles.ts | 0 .../map => map/src/hooks}/useNativeMap.ts | 4 +- .../hooks/map => map/src/hooks}/useWebMap.ts | 4 +- packages/map/src/index.tsx | 59 +++ .../{app/components/map => map/src}/models.ts | 0 packages/map/src/types.ts | 5 + .../map => map/src}/useGpxUpload.tsx | 0 .../map => map/src}/useMapPreview.tsx | 10 +- .../src}/utils/fileSaver/fileSaver.native.ts | 0 .../src}/utils/fileSaver/fileSaver.web.ts | 0 packages/map/src/utils/isObjectEmpty.ts | 9 + packages/map/src/utils/mapFunctions.ts | 452 ++++++++++++++++++ packages/map/tsconfig.json | 9 + yarn.lock | 19 + 34 files changed, 969 insertions(+), 268 deletions(-) delete mode 100644 packages/app/components/map/MapContainer.native.tsx delete mode 100644 packages/app/components/map/MapContainer.tsx delete mode 100644 packages/app/components/map/index.tsx delete mode 100644 packages/app/hooks/map/index.ts create mode 100644 packages/map/.eslintrc.js create mode 100644 packages/map/CHANGELOG.md create mode 100644 packages/map/README.md create mode 100755 packages/map/package.json rename packages/{app/components/map => map/src}/Map.native.tsx (97%) rename packages/{app/components/map => map/src}/Map.tsx (71%) rename packages/{app/components/map => map/src}/MapButtonsOverlay.tsx (69%) rename packages/{app/components/map => map/src}/MapPreview.tsx (81%) create mode 100644 packages/map/src/hooks/useCustomStyles.ts rename packages/{app/hooks/map => map/src/hooks}/useNativeMap.ts (98%) rename packages/{app/hooks/map => map/src/hooks}/useWebMap.ts (99%) create mode 100644 packages/map/src/index.tsx rename packages/{app/components/map => map/src}/models.ts (100%) create mode 100644 packages/map/src/types.ts rename packages/{app/components/map => map/src}/useGpxUpload.tsx (100%) rename packages/{app/components/map => map/src}/useMapPreview.tsx (93%) rename packages/{app => map/src}/utils/fileSaver/fileSaver.native.ts (100%) rename packages/{app => map/src}/utils/fileSaver/fileSaver.web.ts (100%) create mode 100644 packages/map/src/utils/isObjectEmpty.ts create mode 100644 packages/map/src/utils/mapFunctions.ts create mode 100644 packages/map/tsconfig.json diff --git a/apps/vite/src/routeTree.gen.ts b/apps/vite/src/routeTree.gen.ts index 968fb50c5..8e9dfcd1e 100644 --- a/apps/vite/src/routeTree.gen.ts +++ b/apps/vite/src/routeTree.gen.ts @@ -174,94 +174,163 @@ const ProfileSettingsIndexLazyRoute = ProfileSettingsIndexLazyImport.update({ declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { + id: '/' + path: '/' + fullPath: '/' preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } '/destination/query': { + id: '/destination/query' + path: '/destination/query' + fullPath: '/destination/query' preLoaderRoute: typeof DestinationQueryLazyImport parentRoute: typeof rootRoute } '/item/$itemId': { + id: '/item/$itemId' + path: '/item/$itemId' + fullPath: '/item/$itemId' preLoaderRoute: typeof ItemItemIdLazyImport parentRoute: typeof rootRoute } '/pack/$id': { + id: '/pack/$id' + path: '/pack/$id' + fullPath: '/pack/$id' preLoaderRoute: typeof PackIdLazyImport parentRoute: typeof rootRoute } '/pack/create': { + id: '/pack/create' + path: '/pack/create' + fullPath: '/pack/create' preLoaderRoute: typeof PackCreateLazyImport parentRoute: typeof rootRoute } '/profile/$id': { + id: '/profile/$id' + path: '/profile/$id' + fullPath: '/profile/$id' preLoaderRoute: typeof ProfileIdLazyImport parentRoute: typeof rootRoute } '/trip/$tripId': { + id: '/trip/$tripId' + path: '/trip/$tripId' + fullPath: '/trip/$tripId' preLoaderRoute: typeof TripTripIdLazyImport parentRoute: typeof rootRoute } '/trip/create': { + id: '/trip/create' + path: '/trip/create' + fullPath: '/trip/create' preLoaderRoute: typeof TripCreateLazyImport parentRoute: typeof rootRoute } '/about/': { + id: '/about/' + path: '/about' + fullPath: '/about' preLoaderRoute: typeof AboutIndexLazyImport parentRoute: typeof rootRoute } '/appearance/': { + id: '/appearance/' + path: '/appearance' + fullPath: '/appearance' preLoaderRoute: typeof AppearanceIndexLazyImport parentRoute: typeof rootRoute } '/dashboard/': { + id: '/dashboard/' + path: '/dashboard' + fullPath: '/dashboard' preLoaderRoute: typeof DashboardIndexLazyImport parentRoute: typeof rootRoute } '/feed/': { + id: '/feed/' + path: '/feed' + fullPath: '/feed' preLoaderRoute: typeof FeedIndexLazyImport parentRoute: typeof rootRoute } '/items/': { + id: '/items/' + path: '/items' + fullPath: '/items' preLoaderRoute: typeof ItemsIndexLazyImport parentRoute: typeof rootRoute } '/map/': { + id: '/map/' + path: '/map' + fullPath: '/map' preLoaderRoute: typeof MapIndexLazyImport parentRoute: typeof rootRoute } '/maps/': { + id: '/maps/' + path: '/maps' + fullPath: '/maps' preLoaderRoute: typeof MapsIndexLazyImport parentRoute: typeof rootRoute } '/packs/': { + id: '/packs/' + path: '/packs' + fullPath: '/packs' preLoaderRoute: typeof PacksIndexLazyImport parentRoute: typeof rootRoute } '/password-reset/': { + id: '/password-reset/' + path: '/password-reset' + fullPath: '/password-reset' preLoaderRoute: typeof PasswordResetIndexLazyImport parentRoute: typeof rootRoute } '/privacy/': { + id: '/privacy/' + path: '/privacy' + fullPath: '/privacy' preLoaderRoute: typeof PrivacyIndexLazyImport parentRoute: typeof rootRoute } '/profile/': { + id: '/profile/' + path: '/profile' + fullPath: '/profile' preLoaderRoute: typeof ProfileIndexLazyImport parentRoute: typeof rootRoute } '/register/': { + id: '/register/' + path: '/register' + fullPath: '/register' preLoaderRoute: typeof RegisterIndexLazyImport parentRoute: typeof rootRoute } '/sign-in/': { + id: '/sign-in/' + path: '/sign-in' + fullPath: '/sign-in' preLoaderRoute: typeof SignInIndexLazyImport parentRoute: typeof rootRoute } '/trips/': { + id: '/trips/' + path: '/trips' + fullPath: '/trips' preLoaderRoute: typeof TripsIndexLazyImport parentRoute: typeof rootRoute } '/profile/settings/': { + id: '/profile/settings/' + path: '/profile/settings' + fullPath: '/profile/settings' preLoaderRoute: typeof ProfileSettingsIndexLazyImport parentRoute: typeof rootRoute } @@ -270,7 +339,7 @@ declare module '@tanstack/react-router' { // Create and export the route tree -export const routeTree = rootRoute.addChildren([ +export const routeTree = rootRoute.addChildren({ IndexRoute, DestinationQueryLazyRoute, ItemItemIdLazyRoute, @@ -294,6 +363,110 @@ export const routeTree = rootRoute.addChildren([ SignInIndexLazyRoute, TripsIndexLazyRoute, ProfileSettingsIndexLazyRoute, -]) +}) /* prettier-ignore-end */ + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/destination/query", + "/item/$itemId", + "/pack/$id", + "/pack/create", + "/profile/$id", + "/trip/$tripId", + "/trip/create", + "/about/", + "/appearance/", + "/dashboard/", + "/feed/", + "/items/", + "/map/", + "/maps/", + "/packs/", + "/password-reset/", + "/privacy/", + "/profile/", + "/register/", + "/sign-in/", + "/trips/", + "/profile/settings/" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/destination/query": { + "filePath": "destination/query.lazy.tsx" + }, + "/item/$itemId": { + "filePath": "item/$itemId.lazy.tsx" + }, + "/pack/$id": { + "filePath": "pack/$id.lazy.tsx" + }, + "/pack/create": { + "filePath": "pack/create.lazy.tsx" + }, + "/profile/$id": { + "filePath": "profile/$id.lazy.tsx" + }, + "/trip/$tripId": { + "filePath": "trip/$tripId.lazy.tsx" + }, + "/trip/create": { + "filePath": "trip/create.lazy.tsx" + }, + "/about/": { + "filePath": "about/index.lazy.tsx" + }, + "/appearance/": { + "filePath": "appearance/index.lazy.tsx" + }, + "/dashboard/": { + "filePath": "dashboard/index.lazy.tsx" + }, + "/feed/": { + "filePath": "feed/index.lazy.tsx" + }, + "/items/": { + "filePath": "items/index.lazy.tsx" + }, + "/map/": { + "filePath": "map/index.lazy.tsx" + }, + "/maps/": { + "filePath": "maps/index.lazy.tsx" + }, + "/packs/": { + "filePath": "packs/index.lazy.tsx" + }, + "/password-reset/": { + "filePath": "password-reset/index.lazy.tsx" + }, + "/privacy/": { + "filePath": "privacy/index.lazy.tsx" + }, + "/profile/": { + "filePath": "profile/index.lazy.tsx" + }, + "/register/": { + "filePath": "register/index.lazy.tsx" + }, + "/sign-in/": { + "filePath": "sign-in/index.lazy.tsx" + }, + "/trips/": { + "filePath": "trips/index.lazy.tsx" + }, + "/profile/settings/": { + "filePath": "profile/settings/index.lazy.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/packages/app/components/destination/index.tsx b/packages/app/components/destination/index.tsx index dd849ec66..dddde09d6 100644 --- a/packages/app/components/destination/index.tsx +++ b/packages/app/components/destination/index.tsx @@ -1,11 +1,9 @@ import React from 'react'; import { Platform, ScrollView, View } from 'react-native'; -import { RButton, RStack, RText as OriginalRText } from '@packrat/ui'; +import { RButton, RText as OriginalRText } from '@packrat/ui'; import useTheme from '../../hooks/useTheme'; -import { MapContainer } from 'app/components/map'; -import { defaultShape } from '../../utils/mapFunctions'; +import { Map } from '@packrat/map'; import LargeCard from '../card/LargeCard'; -import WeatherCard from '../weather/WeatherCard'; import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'; import useCustomStyles from 'app/hooks/useCustomStyles'; import { @@ -91,7 +89,7 @@ export const DestinationPage = () => { } as any, }); - const shape = geoJSON ?? defaultShape; + const shape = geoJSON; interface SearchResult { properties: { @@ -129,7 +127,7 @@ export const DestinationPage = () => { } }; - const map = () => ; + const map = () => ; return ( @@ -187,7 +185,7 @@ export const DestinationPage = () => { geoJSON={geoJSON} selectedSearchResult={currentDestination} /> - {/* ( { ContentComponent={map} contentProps={{ shape }} type="map" - /> */} + /> )} diff --git a/packages/app/components/map/MapContainer.native.tsx b/packages/app/components/map/MapContainer.native.tsx deleted file mode 100644 index a739fb64e..000000000 --- a/packages/app/components/map/MapContainer.native.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; - -import { View, Platform } from 'react-native'; -import { isObjectEmpty } from '../../utils/isObjectEmpty'; -import { defaultShape } from '../../utils/mapFunctions'; -import useTheme from '../../hooks/useTheme'; -import NativeMap from './Map'; -import useCustomStyles from 'app/hooks/useCustomStyles'; - -export function MapContainer({ shape }) { - const { enableDarkMode, enableLightMode, isDark, isLight, currentTheme } = - useTheme(); - const styles = useCustomStyles(loadStyles); - if (!shape || isObjectEmpty(shape)) { - shape = defaultShape; - } - - if (Platform.OS === 'android' || Platform.OS === 'ios') { - return ( - - - - ); - } -} - -export default MapContainer; - -const loadStyles = () => ({ - webContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - height: 'fit-content', - }, - nativeContainer: { - width: '100%', - marginBottom: 20, - paddingHorizontal: 5, - }, -}); diff --git a/packages/app/components/map/MapContainer.tsx b/packages/app/components/map/MapContainer.tsx deleted file mode 100644 index 58f15f0dc..000000000 --- a/packages/app/components/map/MapContainer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -import { View, Platform } from 'react-native'; - -import WebMap from './Map'; -import { isObjectEmpty } from '../../utils/isObjectEmpty'; -import { defaultShape } from '../../utils/mapFunctions'; -import useCustomStyles from 'app/hooks/useCustomStyles'; - -export function MapContainer({ shape }) { - if (!shape || isObjectEmpty(shape)) { - shape = defaultShape; - } - const styles = useCustomStyles(loadStyles); - - if (Platform.OS === 'web') { - return ( - - - - ); - } -} - -export default MapContainer; - -const loadStyles = () => ({ - webContainer: { - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginVertical: 10, - width: '100%', - height: 400, - borderRadius: 10, - }, -}); diff --git a/packages/app/components/map/index.tsx b/packages/app/components/map/index.tsx deleted file mode 100644 index b8f49b1eb..000000000 --- a/packages/app/components/map/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './MapContainer'; -export { default as Map } from './Map'; diff --git a/packages/app/components/trip/TripCard.tsx b/packages/app/components/trip/TripCard.tsx index 03b452298..e55e4826d 100644 --- a/packages/app/components/trip/TripCard.tsx +++ b/packages/app/components/trip/TripCard.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { RCard as OriginalRCard, RParagraph as OriginalRParagraph, @@ -10,7 +11,7 @@ import useCustomStyles from 'app/hooks/useCustomStyles'; import useTheme from '../../hooks/useTheme'; import { theme } from '../../theme/index'; import Carousel from '../carousel'; -import MapContainer from '../map/MapContainer'; +import { Map } from '@packrat/map'; import { PlacesAutocomplete } from '../PlacesAutocomplete/PlacesAutocomplete'; interface TripCardProps { @@ -101,11 +102,11 @@ export default function TripCard({ Loading.... ) : ( - + ) ) : isSearch ? ( // - //because handle select is not defined here + // because handle select is not defined here ) : ( diff --git a/packages/app/components/trip/TripCards/TripMapCard.tsx b/packages/app/components/trip/TripCards/TripMapCard.tsx index aebc2588e..69e00e388 100644 --- a/packages/app/components/trip/TripCards/TripMapCard.tsx +++ b/packages/app/components/trip/TripCards/TripMapCard.tsx @@ -1,14 +1,14 @@ import { ErrorBoundary, RStack, RText } from '@packrat/ui'; -import MapContainer from 'app/components/map/MapContainer'; +import { Map } from '@packrat/map'; import useTheme from 'app/hooks/useTheme'; import { TripCardBase } from './TripCardBase'; import { FontAwesome5 } from '@expo/vector-icons'; import React from 'react'; -type TripMapCardProps = { +interface TripMapCardProps { isLoading?: boolean; shape: any; -}; +} export const TripMapCard = ({ isLoading, shape }: TripMapCardProps) => { const { currentTheme } = useTheme(); @@ -31,7 +31,7 @@ export const TripMapCard = ({ isLoading, shape }: TripMapCardProps) => { ) : ( - + )} diff --git a/packages/app/hooks/map/index.ts b/packages/app/hooks/map/index.ts deleted file mode 100644 index eff47360c..000000000 --- a/packages/app/hooks/map/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useWebMap } from './useWebMap'; -export { useNativeMap } from './useNativeMap'; diff --git a/packages/app/package.json b/packages/app/package.json index 4e1b34761..714e8f340 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -32,6 +32,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/resolvers": "^3.3.4", "@packrat/crosspath": "*", + "@packrat/map": "*", "@packrat/ui": "*", "@packrat/validations": "*", "@react-native-async-storage/async-storage": "^1.23.1", diff --git a/packages/app/screens/maps/index.tsx b/packages/app/screens/maps/index.tsx index f89f39109..863cf76ba 100644 --- a/packages/app/screens/maps/index.tsx +++ b/packages/app/screens/maps/index.tsx @@ -1,13 +1,13 @@ import { Modal, Text, View, Image } from 'react-native'; import { offlineManager } from '@rnmapbox/maps'; -import { useState, useCallback } from 'react'; +import React, { useState, useCallback } from 'react'; import { useFocusEffect } from 'expo-router'; import { TouchableOpacity } from 'react-native-gesture-handler'; import useTheme from 'app/hooks/useTheme'; import { api } from 'app/constants/api'; import { RButton, RScrollView, RStack } from '@packrat/ui'; import useCustomStyles from 'app/hooks/useCustomStyles'; -import { Map } from 'app/components/map'; +import { Map } from '@packrat/map'; import { useAuthUserToken, useUserQuery } from 'app/modules/auth'; import type OfflinePack from '@rnmapbox/maps/lib/typescript/src/modules/offline/OfflinePack'; import { disableScreen } from 'app/hoc/disableScreen'; diff --git a/packages/app/utils/isObjectEmpty.ts b/packages/app/utils/isObjectEmpty.ts index c3fc4f793..175cfd801 100644 --- a/packages/app/utils/isObjectEmpty.ts +++ b/packages/app/utils/isObjectEmpty.ts @@ -4,6 +4,6 @@ * @param {Object} obj - The object to check. * @return {boolean} - True if the object is empty, false otherwise. */ -export function isObjectEmpty(obj: Object): boolean { +export function isObjectEmpty(obj: Record): boolean { return Object.keys(obj).length === 0; } diff --git a/packages/config/src/index.js b/packages/config/src/index.js index 03a554dde..6e0330ce1 100644 --- a/packages/config/src/index.js +++ b/packages/config/src/index.js @@ -6,10 +6,7 @@ import { viteSource } from './sources/vite'; */ // Simplifying environment variable access using logical OR -const API_URL = - viteSource.VITE_PUBLIC_API_URL || - process.env.NEXT_PUBLIC_API_URL || - process.env.EXPO_PUBLIC_API_URL; +const API_URL = 'http://192.168.100.12:8787/api'; const WEB_CLIENT_ID = viteSource.VITE_PUBLIC_WEB_CLIENT_ID || process.env.NEXT_PUBLIC_WEB_CLIENT_ID || diff --git a/packages/map/.eslintrc.js b/packages/map/.eslintrc.js new file mode 100644 index 000000000..bee952fb0 --- /dev/null +++ b/packages/map/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc-front.js'], +}; diff --git a/packages/map/CHANGELOG.md b/packages/map/CHANGELOG.md new file mode 100644 index 000000000..fdf54cc38 --- /dev/null +++ b/packages/map/CHANGELOG.md @@ -0,0 +1,7 @@ +# @packrat/ui + +## 0.0.1 + +### Patch Changes + +- d50da32: setting up initial version diff --git a/packages/map/README.md b/packages/map/README.md new file mode 100644 index 000000000..f46a2b074 --- /dev/null +++ b/packages/map/README.md @@ -0,0 +1,33 @@ +# @packrat/map + +A cross platform map with mapbox + +## Installation + +```sh +npm install @packrat/map +``` + +## Usage + + +```js +import { multiply } from '@packrat/map'; + +// ... + +const result = await multiply(3, 7); +``` + + +## Contributing + +See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. + +## License + +MIT + +--- + +Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob) diff --git a/packages/map/package.json b/packages/map/package.json new file mode 100755 index 000000000..b4093333b --- /dev/null +++ b/packages/map/package.json @@ -0,0 +1,30 @@ +{ + "files": [ + "types", + "dist" + ], + "main": "src/index.tsx", + "module:jsx": "src", + "name": "@packrat/map", + "peerDependencies": { + "@expo/vector-icons": "*", + "@packrat/config": "*", + "@packrat/ui": "*", + "@react-native-community/geolocation": "*", + "@rnmapbox/maps": "*", + "@tmcw/togeojson": "*", + "expo-document-picker": "*", + "expo-file-system": "*", + "file-saver": "*", + "mapbox-gl": "*", + "react": "*", + "react-native": "*", + "togpx": "*", + "xmldom": "*" + }, + "private": true, + "sideEffects": [ + "*.css" + ], + "version": "0.0.1" +} \ No newline at end of file diff --git a/packages/app/components/map/Map.native.tsx b/packages/map/src/Map.native.tsx similarity index 97% rename from packages/app/components/map/Map.native.tsx rename to packages/map/src/Map.native.tsx index 8af80cb33..5f8aecd0f 100644 --- a/packages/app/components/map/Map.native.tsx +++ b/packages/map/src/Map.native.tsx @@ -6,6 +6,7 @@ import { Modal, Alert, Linking, + StyleSheet, } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import Mapbox from '@rnmapbox/maps'; @@ -19,7 +20,6 @@ import { import { MAPBOX_ACCESS_TOKEN } from '@packrat/config'; -import { theme } from '../../theme'; import { isLineString, isPoint, @@ -28,16 +28,15 @@ import { multiPolygonBounds, validateCoordinates, validateShape, -} from '../../utils/mapFunctions'; +} from './utils/mapFunctions'; import MapButtonsOverlay from './MapButtonsOverlay'; import { gpx as toGeoJSON } from '@tmcw/togeojson'; import { useNativeMap } from 'app/hooks/map/useNativeMap'; -import useCustomStyles from 'app/hooks/useCustomStyles'; import * as DocumentPicker from 'expo-document-picker'; import * as FileSystem from 'expo-file-system'; import { DOMParser } from 'xmldom'; -import { MapProps } from './models'; +import { type MapProps } from './models'; import { useUserQuery } from 'app/modules/auth'; import { useUpdateUser } from 'app/modules/user'; @@ -76,7 +75,7 @@ const NativeMap: React.FC = ({ const { user, refetch } = useUserQuery(); console.log({ user }); const updateUser = useUpdateUser(); - const styles = useCustomStyles(loadStyles); + const styles = StyleSheet.create(loadStyles); const { camera, mapViewRef, @@ -153,7 +152,7 @@ const NativeMap: React.FC = ({ [mapName.toLowerCase()]: downloadOptions, }, }) - .then(() => refetch()) + .then(async () => refetch()) .then(() => { onDownload(downloadOptions); }) @@ -429,7 +428,7 @@ const NativeMap: React.FC = ({ ); }; -const loadStyles = () => ({ +const loadStyles = { page: { flex: 1, flexDirection: 'column', @@ -469,7 +468,7 @@ const loadStyles = () => ({ position: 'absolute', bottom: 10, right: 10, - backgroundColor: theme.colors.primary, + backgroundColor: 'blue', // theme.colors.primary, borderRadius: 50, width: 45, height: 45, @@ -477,10 +476,10 @@ const loadStyles = () => ({ alignItems: 'center', }, mapNameFieldErrorMessage: { - color: theme.colors.error, + color: 'red', // theme.colors.error, fontStyle: 'italic', fontSize: 12, }, -}); +}; export default NativeMap; diff --git a/packages/app/components/map/Map.tsx b/packages/map/src/Map.tsx similarity index 71% rename from packages/app/components/map/Map.tsx rename to packages/map/src/Map.tsx index e80bbe1ce..231ae90f4 100644 --- a/packages/app/components/map/Map.tsx +++ b/packages/map/src/Map.tsx @@ -1,22 +1,19 @@ import { MAPBOX_ACCESS_TOKEN } from '@packrat/config'; -import { useWebMap } from 'app/hooks/map/useWebMap'; -import useCustomStyles from 'app/hooks/useCustomStyles'; +import { useWebMap } from './hooks/useWebMap'; import mapboxgl from 'mapbox-gl'; -import React, { FC, useState } from 'react'; -import { Modal, View } from 'react-native'; -import { isPolygonOrMultiPolygon } from '../../utils/mapFunctions'; +import React, { type FC, useState } from 'react'; +import { Modal, View, StyleSheet } from 'react-native'; +import { isPolygonOrMultiPolygon } from './utils/mapFunctions'; import MapButtonsOverlay from './MapButtonsOverlay'; import MapPreview from './MapPreview'; import useGpxUpload from './useGpxUpload'; -import { MapProps } from './models'; +import { type MapProps } from './models'; mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN; -const DESTINATION = 'destination'; -const TRIP = 'trip'; const WebMap: FC = ({ shape: shapeProp }) => { const [downloadable, setDownloadable] = useState(false); - const styles = useCustomStyles(loadStyles); + const styles = StyleSheet.create(loadStyles); const { shape, setShape, @@ -36,18 +33,18 @@ const WebMap: FC = ({ shape: shapeProp }) => { const element = ( - {showModal || isPolygonOrMultiPolygon(shape) ? ( - - ) : ( + {/* {showModal || isPolygonOrMultiPolygon(shape) ? ( */} + + {/* ) : ( - )} + )} */} = ({ shape: shapeProp }) => { // ); }; -const loadStyles = () => ({ +const loadStyles = { container: { alignItems: 'center', justifyContent: 'center', @@ -95,11 +92,12 @@ const loadStyles = () => ({ }, map: { width: '100%', - minHeight: '100vh', // Adjust the height to your needs + minHeight: '100%', + // minHeight: '100vh', // Adjust the height to your needs }, modal: { alignItems: 'center', }, -}); +} as const; export default WebMap; diff --git a/packages/app/components/map/MapButtonsOverlay.tsx b/packages/map/src/MapButtonsOverlay.tsx similarity index 69% rename from packages/app/components/map/MapButtonsOverlay.tsx rename to packages/map/src/MapButtonsOverlay.tsx index 15404eb8f..ed9f710e4 100644 --- a/packages/app/components/map/MapButtonsOverlay.tsx +++ b/packages/map/src/MapButtonsOverlay.tsx @@ -6,6 +6,7 @@ import { Modal, View, Alert, + StyleSheet, Platform, } from 'react-native'; import { @@ -13,9 +14,7 @@ import { MaterialCommunityIcons, FontAwesome5, } from '@expo/vector-icons'; -import useTheme from '../../hooks/useTheme'; -import { mapboxStyles } from '../../utils/mapFunctions'; -import useCustomStyles from 'app/hooks/useCustomStyles'; +import { mapboxStyles } from './utils/mapFunctions'; interface MapButtonsOverlayProps { mapFullscreen: boolean; @@ -47,9 +46,7 @@ const MapButtonsOverlay = ({ navigateToMaps, }: MapButtonsOverlayProps) => { const [showStyleOptions, setShowStyleOptions] = useState(false); - const { enableDarkMode, enableLightMode, isDark, isLight, currentTheme } = - useTheme(); - const styles = useCustomStyles(loadStyles); + const styles = StyleSheet.create(loadStyles); const handleStyleOptionPress = () => { setShowStyleOptions(!showStyleOptions); }; @@ -61,7 +58,7 @@ const MapButtonsOverlay = ({ * @return {type} undefined */ const handleStyleSelection = (style) => { - handleChangeMapStyle(style); + handleChangeMapStyle?.call(style); setShowStyleOptions(false); }; @@ -194,7 +191,7 @@ const MapButtonsOverlay = ({ position: 'absolute', bottom: 80, right: 10, - backgroundColor: currentTheme.colors.white, + backgroundColor: 'white', borderRadius: 30, zIndex: 1, }} @@ -236,110 +233,107 @@ const MapButtonsOverlay = ({ ); }; -const loadStyles = (theme) => { - const { currentTheme } = theme; - return { - container: { - alignItems: 'center', - justifyContent: 'center', - height: 400, - width: '100%', - borderRadius: 10, - }, - map: { - width: '100%', - minHeight: '100vh', // Adjust the height to your needs - }, - stylePicker: { - // Style Picker Button - position: 'absolute', - top: 10, - left: 10, - width: 40, - height: 40, - alignItems: 'center', - justifyContent: 'center', - borderRadius: 20, - backgroundColor: currentTheme.colors.white, - }, - styleModalContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - styleModalContent: { - backgroundColor: currentTheme.colors.white, - borderRadius: 8, - padding: 10, - }, - styleOption: { - paddingVertical: 8, - paddingHorizontal: 16, - }, - styleOptionText: { - fontSize: 16, - fontWeight: 'bold', - }, - locationButton: { - alignItems: 'center', - justifyContent: 'center', - width: 40, - height: 40, - position: 'absolute', - bottom: 30, - right: 10, - backgroundColor: currentTheme.colors.white, - borderRadius: 30, - zIndex: 1, - }, - headerBtnView: { - justifyContent: 'center', - alignItems: 'center', - borderRadius: 30, - marginTop: 30, - backgroundColor: currentTheme.colors.white, - }, - enterFullScreenBtn: { - width: 40, - height: 40, - position: 'absolute', - bottom: 10, - right: 10, - }, - exitFullscreenBtn: { - width: 40, - height: 40, - position: 'absolute', - top: 10, - right: 10, - }, - fullScreen: { - width: Platform.OS == 'web' ? '25%' : '70%', - height: 40, - padding: 10, - backgroundColor: currentTheme.colors.white, - position: 'absolute', - bottom: 30, - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - alignSelf: 'center', - borderRadius: 20, - }, - downloadIcon: { - width: 21, - height: 21, - }, - downloadText: { - fontSize: 15, - fontWeight: '500', - marginRight: 8, - }, - modal: { - alignItems: 'center', - }, - }; -}; +const loadStyles = { + container: { + alignItems: 'center', + justifyContent: 'center', + height: 400, + width: '100%', + borderRadius: 10, + }, + map: { + width: '100%', + minHeight: '100%', // Adjust the height to your needs + }, + stylePicker: { + // Style Picker Button + position: 'absolute', + top: 10, + left: 10, + width: 40, + height: 40, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 20, + backgroundColor: 'white', + }, + styleModalContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + styleModalContent: { + backgroundColor: 'white', + borderRadius: 8, + padding: 10, + }, + styleOption: { + paddingVertical: 8, + paddingHorizontal: 16, + }, + styleOptionText: { + fontSize: 16, + fontWeight: 'bold', + }, + locationButton: { + alignItems: 'center', + justifyContent: 'center', + width: 40, + height: 40, + position: 'absolute', + bottom: 30, + right: 10, + backgroundColor: 'white', + borderRadius: 30, + zIndex: 1, + }, + headerBtnView: { + justifyContent: 'center', + alignItems: 'center', + borderRadius: 30, + marginTop: 30, + backgroundColor: 'white', + }, + enterFullScreenBtn: { + width: 40, + height: 40, + position: 'absolute', + bottom: 10, + right: 10, + }, + exitFullscreenBtn: { + width: 40, + height: 40, + position: 'absolute', + top: 10, + right: 10, + }, + fullScreen: { + width: Platform.OS == 'web' ? '25%' : '70%', + height: 40, + padding: 10, + backgroundColor: 'white', + position: 'absolute', + bottom: 30, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + alignSelf: 'center', + borderRadius: 20, + }, + downloadIcon: { + width: 21, + height: 21, + }, + downloadText: { + fontSize: 15, + fontWeight: '500', + marginRight: 8, + }, + modal: { + alignItems: 'center', + }, +} as const; export default MapButtonsOverlay; diff --git a/packages/app/components/map/MapPreview.tsx b/packages/map/src/MapPreview.tsx similarity index 81% rename from packages/app/components/map/MapPreview.tsx rename to packages/map/src/MapPreview.tsx index 2c1385239..d80ac4b1d 100644 --- a/packages/app/components/map/MapPreview.tsx +++ b/packages/map/src/MapPreview.tsx @@ -1,13 +1,16 @@ +import React from 'react'; import { RImage } from '@packrat/ui'; import { useProcessedShape, useMapPreviewData } from './useMapPreview'; import { useAuthUserToken } from 'app/modules/auth'; -export default function MapPreview({ shape }) { + +export default function MapPreview({ shape }: { shape: unknown }) { const processedShape = useProcessedShape(shape); const { token } = useAuthUserToken(); const mapPreviewData: any = useMapPreviewData(shape, processedShape); if (!mapPreviewData) return null; + console.error({ mapPreviewData, token }); return ( { diff --git a/packages/map/src/index.tsx b/packages/map/src/index.tsx new file mode 100644 index 000000000..fb345ac19 --- /dev/null +++ b/packages/map/src/index.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { View } from 'react-native'; +import { isObjectEmpty } from './utils/isObjectEmpty'; +import { defaultShape } from './utils/mapFunctions'; +import PlatformMap from './Map'; +import type { MapProps } from './models'; + +export const Map: React.FC = ({ + shape, // : shapeProp, + onExitFullScreen, + mapName: predefinedMapName, + forceFullScreen = false, + shouldEnableDownload = true, +}) => { + if (!shape || isObjectEmpty(shape)) { + shape = defaultShape; + } + + return ( + + + + ); +}; + +export default Map; + +const loadStyles = () => ({ + webContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginVertical: 10, + width: '100%', + height: 'fit-content', + // height: 400, + borderRadius: 10, + }, + nativeContainer: { + width: '100%', + marginBottom: 20, + paddingHorizontal: 5, + }, +}); diff --git a/packages/app/components/map/models.ts b/packages/map/src/models.ts similarity index 100% rename from packages/app/components/map/models.ts rename to packages/map/src/models.ts diff --git a/packages/map/src/types.ts b/packages/map/src/types.ts new file mode 100644 index 000000000..d904ab09f --- /dev/null +++ b/packages/map/src/types.ts @@ -0,0 +1,5 @@ +import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'; + +export type NamedStyles = { + [P in keyof T]: ViewStyle | TextStyle | ImageStyle; +}; diff --git a/packages/app/components/map/useGpxUpload.tsx b/packages/map/src/useGpxUpload.tsx similarity index 100% rename from packages/app/components/map/useGpxUpload.tsx rename to packages/map/src/useGpxUpload.tsx diff --git a/packages/app/components/map/useMapPreview.tsx b/packages/map/src/useMapPreview.tsx similarity index 93% rename from packages/app/components/map/useMapPreview.tsx rename to packages/map/src/useMapPreview.tsx index b0a418b16..0984826b0 100644 --- a/packages/app/components/map/useMapPreview.tsx +++ b/packages/map/src/useMapPreview.tsx @@ -4,13 +4,11 @@ import { isPolygonOrMultiPolygon, processShapeData, isPoint, -} from '../../utils/mapFunctions'; -import { api } from '../../constants/api'; +} from './utils/mapFunctions'; +import { API_URL } from '@packrat/config'; import { useState, useEffect } from 'react'; -interface Feature { - [key: string]: any; -} +type Feature = Record; interface MapPreviewData { isPoint: boolean; @@ -90,7 +88,7 @@ const useMapPreviewData = (shape, processedShape) => { coordinates: [lng, lat], } = shape.features[0].geometry; - const mapPreviewEndpoint = `${api}/mapPreview`; + const mapPreviewEndpoint = `${API_URL}/mapPreview`; const data = { isPoint: isPoint(shape), diff --git a/packages/app/utils/fileSaver/fileSaver.native.ts b/packages/map/src/utils/fileSaver/fileSaver.native.ts similarity index 100% rename from packages/app/utils/fileSaver/fileSaver.native.ts rename to packages/map/src/utils/fileSaver/fileSaver.native.ts diff --git a/packages/app/utils/fileSaver/fileSaver.web.ts b/packages/map/src/utils/fileSaver/fileSaver.web.ts similarity index 100% rename from packages/app/utils/fileSaver/fileSaver.web.ts rename to packages/map/src/utils/fileSaver/fileSaver.web.ts diff --git a/packages/map/src/utils/isObjectEmpty.ts b/packages/map/src/utils/isObjectEmpty.ts new file mode 100644 index 000000000..c3fc4f793 --- /dev/null +++ b/packages/map/src/utils/isObjectEmpty.ts @@ -0,0 +1,9 @@ +/** + * Determines if an object is empty. + * + * @param {Object} obj - The object to check. + * @return {boolean} - True if the object is empty, false otherwise. + */ +export function isObjectEmpty(obj: Object): boolean { + return Object.keys(obj).length === 0; +} diff --git a/packages/map/src/utils/mapFunctions.ts b/packages/map/src/utils/mapFunctions.ts new file mode 100644 index 000000000..be1301fb3 --- /dev/null +++ b/packages/map/src/utils/mapFunctions.ts @@ -0,0 +1,452 @@ +import * as Location from 'expo-location'; + +const defaultShape = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [-77.044211, 38.852924], + [-77.045659, 38.860158], + [-77.044232, 38.862326], + [-77.040879, 38.865454], + [-77.039936, 38.867698], + [-77.040338, 38.86943], + [-77.04264, 38.872528], + [-77.03696, 38.878424], + [-77.032309, 38.87937], + [-77.030056, 38.880945], + [-77.027645, 38.881779], + [-77.026946, 38.882645], + [-77.026942, 38.885502], + [-77.028054, 38.887449], + [-77.02806, 38.892088], + [-77.03364, 38.892108], + [-77.033643, 38.899926], + ], + }, + }, + ], +}; + +/** + * Normalize the coordinates. + * + * @param {array} coordinates - The coordinates to be normalized. + * @return {array} The normalized coordinates. + */ +function normalizeCoordinates(coordinates) { + // check if coordinates are nested, flip them if so + if (typeof coordinates[0][0] === 'number') { + // If first value is greater than 90, it's likely in the format of (longitude, latitude) + if (coordinates[0][0] > 90) { + return coordinates.map((coordinate) => [coordinate[1], coordinate[0]]); + } + return coordinates; + } + // if not nested, nest them + // If first value is greater than 90, it's likely in the format of (longitude, latitude) + if (coordinates[0] > 90) { + return [[coordinates[1], coordinates[0]]]; + } + return [[coordinates[0], coordinates[1]]]; +} + +/** + * Converts a Photon GeoJSON object to a Shape object. + * + * @param {object} photonGeoJson - The Photon GeoJSON object to convert. + * @return {object} The converted Shape object. + */ +function convertPhotonGeoJsonToShape(photonGeoJson) { + return { + type: 'FeatureCollection', + features: [photonGeoJson], + }; +} + +/** + * Calculates the minimum and maximum longitude and latitude coordinates of a given shape. + * + * @param {object} shape - The shape object containing the coordinates. + * @return {array} An array representing the minimum and maximum longitude and latitude coordinates. + */ +function getShapeSourceBounds(shape) { + let minLng = Infinity; + let maxLng = -Infinity; + let minLat = Infinity; + let maxLat = -Infinity; + shape.features[0].geometry.coordinates.forEach((coord) => { + const lng = coord[0]; + const lat = coord[1]; + + if (lng < minLng) { + minLng = lng; + } + if (lng > maxLng) { + maxLng = lng; + } + if (lat < minLat) { + minLat = lat; + } + if (lat > maxLat) { + maxLat = lat; + } + }); + + return [ + [minLng, minLat], + [maxLng, maxLat], + ]; +} + +/** + * Handles the shape source load and calculates the zoom level based on the width and height. + * + * @param {object} shape - The shape object containing the coordinates. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @return {number|null} The calculated zoom level or null if there are no coordinates. + */ +function handleShapeSourceLoad(shape, width, height) { + if (shape?.features[0]?.geometry?.coordinates?.length > 1) { + let bounds = getShapeSourceBounds(shape); + if (bounds && bounds[0] && bounds[1]) { + bounds = [bounds[0].concat(bounds[1])]; + return calculateZoomLevel(bounds[0], { width, height }); + } + } + return null; +} + +/** + * Calculates the latitude in radians. + * + * @param {number} lat - The latitude in degrees. + * @return {number} The latitude in radians. + */ +function latRad(lat) { + const sin = Math.sin((lat * Math.PI) / 180); + const radX2 = Math.log((1 + sin) / (1 - sin)) / 2; + return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2; +} + +/** + * Calculates the zoom level of a map based on the size of the map in pixels, + * the size of the world in pixels, and a fraction. + * + * @param {number} mapPx - The size of the map in pixels. + * @param {number} worldPx - The size of the world in pixels. + * @param {number} fraction - The fraction used for the calculation. + * @return {number} The calculated zoom level. + */ +function zoom(mapPx, worldPx, fraction) { + return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2); +} + +/** + * Calculates the zoom level for a given map based on the provided bounds and map dimensions. + * + * @param {Array} bounds - The bounds of the map in the format [south, west, north, east]. + * @param {Object} mapDim - The dimensions of the map in the format {height: number, width: number}. + * @return {number} The calculated zoom level for the map. + */ +function calculateZoomLevel(bounds, mapDim) { + const WORLD_DIM = { height: 256, width: 256 }; + const ne = { lat: bounds[2], lng: bounds[3] }; + const sw = { lat: bounds[0], lng: bounds[1] }; + + const latFraction = (latRad(ne.lat) - latRad(sw.lat)) / Math.PI; + + const lngDiff = ne.lng - sw.lng; + const lngFraction = (lngDiff < 0 ? lngDiff + 360 : lngDiff) / 360; + + const latZoom = zoom(mapDim.height, WORLD_DIM.height, Math.abs(latFraction)); + const lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction); + + return latZoom; +} + +/** + * Finds the center of a trail based on its shape coordinates. + * + * @param {object} shape - The shape object containing trail coordinates. + * @return {array} The center coordinates of the trail. + */ +function findTrailCenter(shape) { + console.log('Finding trail center...', shape); + const trailCoords = shape?.features[0]?.geometry?.coordinates; + + // Flatten the coordinates array for Polygon geometries + + let avgLatitude; + let avgLongitude; + let flattenedCoords; + if (trailCoords[0][0] === undefined) { + avgLongitude = trailCoords[0]; + avgLatitude = trailCoords[1]; + } else if (Array.isArray(trailCoords[0][0][0])) { + flattenedCoords = trailCoords[0].flat(); + + const latitudes = flattenedCoords.map((coord) => coord[1]); + const longitudes = flattenedCoords.map((coord) => coord[0]); + + avgLatitude = latitudes.reduce((a, b) => a + b, 0) / latitudes.length; + avgLongitude = longitudes.reduce((a, b) => a + b, 0) / longitudes.length; + } else { + flattenedCoords = trailCoords[0]; + + const latitudes = flattenedCoords.map((coord) => coord[1]); + const longitudes = flattenedCoords.map((coord) => coord[0]); + + avgLatitude = latitudes.reduce((a, b) => a + b, 0) / latitudes.length; + avgLongitude = longitudes.reduce((a, b) => a + b, 0) / longitudes.length; + } + + console.log('Average latitude:', avgLatitude); + console.log('Average longitude:', avgLongitude); + + return [avgLongitude, avgLatitude]; +} + +/** + * Process the shape data by transforming LineString features into Points. + * + * @param {Object} shape - The shape data to be processed. + * @return {Object} The processed shape data. + */ +const processShapeData = (shape) => { + const processedShape = { ...shape }; + processedShape.features = []; + + shape.features.forEach((feature) => { + if (feature.geometry.type === 'LineString') { + // Make sure coordinates are in the correct format + feature.geometry.coordinates = ensure2DArray( + feature.geometry.coordinates, + ); + + const points = feature.geometry.coordinates.map((coord, index) => { + return { + type: 'Feature', + properties: { + // Add a `meta` property to the first and last points + meta: + index === 0 || index === feature.geometry.coordinates.length - 1 + ? 'end' + : 'middle', + }, + geometry: { + type: 'Point', + coordinates: coord, + }, + }; + }); + + processedShape.features.push(...points); + + // Keep the original LineString feature + processedShape.features.push(feature); + } + }); + + return processedShape; +}; + +/** + * Ensure the input array is a 2D array. + * + * @param {Array} arr - The input array. + * @return {Array} - The 2D array. + */ +const ensure2DArray = (arr) => { + // If the first element of the array is not an array itself, add an additional array layer + if (!Array.isArray(arr[0])) { + return [arr]; + } + // If the array is already 2D, return it as is + return arr; +}; + +const mapboxStyles = [ + { label: 'Outdoors', style: 'mapbox://styles/mapbox/outdoors-v11' }, + { label: 'Street', style: 'mapbox://styles/mapbox/streets-v11' }, + { label: 'Light', style: 'mapbox://styles/mapbox/light-v10' }, + { label: 'Dark', style: 'mapbox://styles/mapbox/dark-v10' }, + { label: 'Satellite', style: 'mapbox://styles/mapbox/satellite-v9' }, + { + label: 'Satellite Street', + style: 'mapbox://styles/mapbox/satellite-streets-v11', + }, +]; + +/** + * Retrieves the current location asynchronously. + * + * @return {Promise} The current location object. + */ +const getLocation = async () => { + const { status } = await Location.requestForegroundPermissionsAsync(); + + if (status !== 'granted') { + alert('Permission to access location was denied'); + return; + } + + const location = await Location.getCurrentPositionAsync({}); + + return location; +}; + +/** + * Checks if a shape is downloadable. + * + * @param {Object} shape - The shape object to check. + * @return {boolean} Returns true if the shape is downloadable, false otherwise. + */ +const isShapeDownloadable = (shape) => { + return shape?.features[0]?.geometry?.coordinates?.length >= 1; +}; + +/** + * Checks if the given shape is a point. + * + * @param {Object} shape - The shape object to be checked. + * @return {boolean} Returns true if the shape is a point, otherwise returns false. + */ +const isPoint = (shape) => { + return shape?.features[0]?.geometry?.type === 'Point'; +}; +/** + * Checks if the given shape is a LineString. + * + * @param {Object} shape - The shape object to be checked. + * @return {boolean} Returns true if the shape is a LineString, otherwise false. + */ +const isLineString = (shape) => { + return shape?.features[0]?.geometry?.type === 'LineString'; +}; + +/** + * Checks if the given shape is a Polygon or MultiPolygon. + * + * @param {object} shape - The shape object to be checked. + * @return {boolean} Returns true if the shape is a Polygon or MultiPolygon, otherwise returns false. + */ +const isPolygonOrMultiPolygon = (shape) => { + return ( + shape?.features[0]?.geometry?.type === 'MultiPolygon' || + shape?.features[0]?.geometry?.type === 'MultiPolygon' + ); +}; + +/** + * Calculates the bounds of a multipolygon. + * + * @param {object} multipolygonData - The multipolygon data. + * @return {array} The center longitude and latitude of the bounds. + */ +const multiPolygonBounds = (multipolygonData) => { + let coordinates = multipolygonData.geometry.coordinates[0]; + if (multipolygonData.geometry.type === 'MultiPolygon') { + coordinates = coordinates[0]; + } + let minX = Infinity; + let maxX = -Infinity; + let minY = Infinity; + let maxY = -Infinity; + + for (const [lng, lat] of coordinates) { + minX = Math.min(minX, lng); + maxX = Math.max(maxX, lng); + minY = Math.min(minY, lat); + maxY = Math.max(maxY, lat); + } + + const centerLng = (minX + maxX) / 2; + const centerLat = (minY + maxY) / 2; + + return [centerLng, centerLat]; +}; + +const validateCoordinates = (coordinates: any) => { + if (!Array.isArray(coordinates)) { + throw new Error('Invalid coordinates: Must be an array.'); + } + coordinates.forEach((coord) => { + if (!Array.isArray(coord)) { + if (typeof coord !== 'number') { + throw new Error( + 'Invalid coordinates: Each coordinate must be a number.', + ); + } + } else { + validateCoordinates(coord); + } + }); +}; + +const validateGeoJSON = (geojson: any) => { + if (!geojson || typeof geojson !== 'object') { + throw new Error('Invalid GeoJSON: Data is not an object.'); + } + if (!geojson.type) { + throw new Error('Invalid GeoJSON: Missing "type" property.'); + } + if (!geojson.features || !Array.isArray(geojson.features)) { + throw new Error('Invalid GeoJSON: Missing or invalid "features" property.'); + } + + geojson.features.forEach((feature) => { + try { + if (feature.geometry && feature.geometry.coordinates) { + if (feature.geometry.type === 'Point') { + validateCoordinates(feature.geometry.coordinates); + } else if ( + feature.geometry.type === 'LineString' || + feature.geometry.type === 'Polygon' + ) { + feature.geometry.coordinates.forEach(validateCoordinates); + } else if (feature.geometry.type === 'MultiPolygon') { + feature.geometry.coordinates.forEach((polygon) => { + polygon.forEach(validateCoordinates); + }); + } + } + } catch (e) { + throw new Error(e); + } + }); +}; + +const validateShape = (shape: any) => { + try { + validateGeoJSON(shape); + } catch (error) { + throw new Error(`Invalid shape: ${error.message}`); + } +}; + +export { + defaultShape, + getShapeSourceBounds, + handleShapeSourceLoad, + latRad, + zoom, + calculateZoomLevel, + findTrailCenter, + processShapeData, + mapboxStyles, + getLocation, + isShapeDownloadable, + convertPhotonGeoJsonToShape, + isPoint, + isLineString, + isPolygonOrMultiPolygon, + multiPolygonBounds, + validateGeoJSON, + validateShape, + validateCoordinates, +}; diff --git a/packages/map/tsconfig.json b/packages/map/tsconfig.json new file mode 100644 index 000000000..bb3fc4e85 --- /dev/null +++ b/packages/map/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base", // not set up yet + "include": ["src"], + "compilerOptions": { + "composite": true, + "jsx": "react-jsx" + }, + "references": [] +} diff --git a/yarn.lock b/yarn.lock index 51d54e98f..de9f44751 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6080,6 +6080,24 @@ __metadata: languageName: unknown linkType: soft +"@packrat/map@npm:*, @packrat/map@workspace:packages/map": + version: 0.0.0-use.local + resolution: "@packrat/map@workspace:packages/map" + peerDependencies: + "@expo/vector-icons": "*" + "@packrat/config": "*" + "@packrat/ui": "*" + "@rnmapbox/maps": "*" + "@tmcw/togeojson": "*" + expo-document-picker: "*" + expo-file-system: "*" + mapbox-gl: "*" + react: "*" + react-native: "*" + xmldom: "*" + languageName: unknown + linkType: soft + "@packrat/playwright@workspace:packages/playwright": version: 0.0.0-use.local resolution: "@packrat/playwright@workspace:packages/playwright" @@ -13387,6 +13405,7 @@ __metadata: "@fortawesome/react-fontawesome": "npm:^0.2.0" "@hookform/resolvers": "npm:^3.3.4" "@packrat/crosspath": "npm:*" + "@packrat/map": "npm:*" "@packrat/ui": "npm:*" "@packrat/validations": "npm:*" "@react-native-async-storage/async-storage": "npm:^1.23.1" From 8686361aef32322cbcf49f24cc7eead61108c22f Mon Sep 17 00:00:00 2001 From: Tadjaur Date: Sun, 25 Aug 2024 08:03:26 +0100 Subject: [PATCH 003/127] fix the map authorization issue --- packages/config/src/index.js | 5 ++- packages/map/package.json | 2 +- packages/map/src/Map.tsx | 24 ++++++------- packages/map/src/MapPreview.tsx | 18 ++++------ .../src/hooks/useGetObjectUrlFromImageUri.ts | 36 +++++++++++++++++++ .../useGpxUpload.ts} | 0 .../useMapPreview.ts} | 2 +- yarn.lock | 3 ++ 8 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 packages/map/src/hooks/useGetObjectUrlFromImageUri.ts rename packages/map/src/{useGpxUpload.tsx => hooks/useGpxUpload.ts} (100%) rename packages/map/src/{useMapPreview.tsx => hooks/useMapPreview.ts} (98%) diff --git a/packages/config/src/index.js b/packages/config/src/index.js index 6e0330ce1..03a554dde 100644 --- a/packages/config/src/index.js +++ b/packages/config/src/index.js @@ -6,7 +6,10 @@ import { viteSource } from './sources/vite'; */ // Simplifying environment variable access using logical OR -const API_URL = 'http://192.168.100.12:8787/api'; +const API_URL = + viteSource.VITE_PUBLIC_API_URL || + process.env.NEXT_PUBLIC_API_URL || + process.env.EXPO_PUBLIC_API_URL; const WEB_CLIENT_ID = viteSource.VITE_PUBLIC_WEB_CLIENT_ID || process.env.NEXT_PUBLIC_WEB_CLIENT_ID || diff --git a/packages/map/package.json b/packages/map/package.json index b4093333b..40bd1ea38 100755 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -27,4 +27,4 @@ "*.css" ], "version": "0.0.1" -} \ No newline at end of file +} diff --git a/packages/map/src/Map.tsx b/packages/map/src/Map.tsx index 231ae90f4..1dba4ff63 100644 --- a/packages/map/src/Map.tsx +++ b/packages/map/src/Map.tsx @@ -6,7 +6,7 @@ import { Modal, View, StyleSheet } from 'react-native'; import { isPolygonOrMultiPolygon } from './utils/mapFunctions'; import MapButtonsOverlay from './MapButtonsOverlay'; import MapPreview from './MapPreview'; -import useGpxUpload from './useGpxUpload'; +import useGpxUpload from './hooks/useGpxUpload'; import { type MapProps } from './models'; mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN; @@ -33,18 +33,18 @@ const WebMap: FC = ({ shape: shapeProp }) => { const element = ( - {/* {showModal || isPolygonOrMultiPolygon(shape) ? ( */} - - {/* ) : ( + {showModal || isPolygonOrMultiPolygon(shape) ? ( + + ) : ( - )} */} + )} ); } diff --git a/packages/map/src/hooks/useGetObjectUrlFromImageUri.ts b/packages/map/src/hooks/useGetObjectUrlFromImageUri.ts new file mode 100644 index 000000000..1d2741304 --- /dev/null +++ b/packages/map/src/hooks/useGetObjectUrlFromImageUri.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import { useAuthUserToken } from 'app/modules/auth'; + +export const useGetObjectUrlFromImageUri = (imageURI: string | null) => { + const { token } = useAuthUserToken(); + const [imageObjectURL, setImageObjectURL] = useState(null); + + useEffect(() => { + if (!imageURI) { + return; + } + const fetchImage = async () => { + try { + const response = await fetch(imageURI, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response.ok) { + const blob = await response.blob(); + const imgURL = URL.createObjectURL(blob); + setImageObjectURL(imgURL); + } else { + console.error('Failed to fetch image'); + } + } catch (error) { + console.error('Error:', error); + } + }; + + fetchImage(); + }, [imageURI, token]); + + return imageObjectURL; +}; diff --git a/packages/map/src/useGpxUpload.tsx b/packages/map/src/hooks/useGpxUpload.ts similarity index 100% rename from packages/map/src/useGpxUpload.tsx rename to packages/map/src/hooks/useGpxUpload.ts diff --git a/packages/map/src/useMapPreview.tsx b/packages/map/src/hooks/useMapPreview.ts similarity index 98% rename from packages/map/src/useMapPreview.tsx rename to packages/map/src/hooks/useMapPreview.ts index 0984826b0..ac8cc33a3 100644 --- a/packages/map/src/useMapPreview.tsx +++ b/packages/map/src/hooks/useMapPreview.ts @@ -4,7 +4,7 @@ import { isPolygonOrMultiPolygon, processShapeData, isPoint, -} from './utils/mapFunctions'; +} from '../utils/mapFunctions'; import { API_URL } from '@packrat/config'; import { useState, useEffect } from 'react'; diff --git a/yarn.lock b/yarn.lock index de9f44751..8cb310347 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6087,13 +6087,16 @@ __metadata: "@expo/vector-icons": "*" "@packrat/config": "*" "@packrat/ui": "*" + "@react-native-community/geolocation": "*" "@rnmapbox/maps": "*" "@tmcw/togeojson": "*" expo-document-picker: "*" expo-file-system: "*" + file-saver: "*" mapbox-gl: "*" react: "*" react-native: "*" + togpx: "*" xmldom: "*" languageName: unknown linkType: soft From 325e30d77c8afbd23e461d627ffd8680bc28daf0 Mon Sep 17 00:00:00 2001 From: Tadjaur Date: Sun, 25 Aug 2024 09:18:55 +0100 Subject: [PATCH 004/127] Fix map preview not loading --- packages/app/components/card/LargeCard.tsx | 7 ++----- packages/map/src/Map.native.tsx | 2 +- packages/map/src/Map.tsx | 2 +- packages/map/src/index.tsx | 1 + server/src/controllers/mapPreview/index.ts | 6 ++---- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/app/components/card/LargeCard.tsx b/packages/app/components/card/LargeCard.tsx index 822c979b5..1868503cf 100644 --- a/packages/app/components/card/LargeCard.tsx +++ b/packages/app/components/card/LargeCard.tsx @@ -126,13 +126,10 @@ const loadStyles = (theme: any) => { flexDirection: 'column', alignItems: 'center', textAlign: 'center', - padding: - Platform.OS === 'web' - ? currentTheme.size.cardPadding - : currentTheme.size.mobilePadding, + padding: Platform.OS === 'web' ? '5%' : currentTheme.size.mobilePadding, paddingHorizontal: currentTheme.padding.paddingInside, marginBottom: 20, - height: Platform.OS === 'web' ? 650 : '23%', + height: Platform.OS === 'web' ? 'calc(min( 80vh, 80vw))' : '23%', minHeight: 350, overflow: 'hidden', }, diff --git a/packages/map/src/Map.native.tsx b/packages/map/src/Map.native.tsx index 5f8aecd0f..a7ff393ad 100644 --- a/packages/map/src/Map.native.tsx +++ b/packages/map/src/Map.native.tsx @@ -32,7 +32,7 @@ import { import MapButtonsOverlay from './MapButtonsOverlay'; import { gpx as toGeoJSON } from '@tmcw/togeojson'; -import { useNativeMap } from 'app/hooks/map/useNativeMap'; +import { useNativeMap } from './hooks/useNativeMap'; import * as DocumentPicker from 'expo-document-picker'; import * as FileSystem from 'expo-file-system'; import { DOMParser } from 'xmldom'; diff --git a/packages/map/src/Map.tsx b/packages/map/src/Map.tsx index 1dba4ff63..a06d7e25e 100644 --- a/packages/map/src/Map.tsx +++ b/packages/map/src/Map.tsx @@ -32,7 +32,7 @@ const WebMap: FC = ({ shape: shapeProp }) => { const handleGpxUpload = useGpxUpload(setShape); const element = ( - + {showModal || isPolygonOrMultiPolygon(shape) ? ( = ({ { } const newResponse = new Response(response.body, { + status: 200, headers: { 'Content-Type': 'image/png', }, }); - console.log('newResponse', newResponse); - - // return newResponse; - return ctx.json(newResponse, 200); + return newResponse; } catch (error) { console.log(error); return ctx.json({ error: error.message }, 400); From 0ffb189f073e7d3f8f290687be1ca280fa7ae727 Mon Sep 17 00:00:00 2001 From: Tadjaur Date: Sun, 25 Aug 2024 10:02:59 +0100 Subject: [PATCH 005/127] add dependencies version of required package --- packages/map/CHANGELOG.md | 7 ------- packages/map/README.md | 17 ++++++----------- packages/map/package.json | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 30 deletions(-) delete mode 100644 packages/map/CHANGELOG.md diff --git a/packages/map/CHANGELOG.md b/packages/map/CHANGELOG.md deleted file mode 100644 index fdf54cc38..000000000 --- a/packages/map/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# @packrat/ui - -## 0.0.1 - -### Patch Changes - -- d50da32: setting up initial version diff --git a/packages/map/README.md b/packages/map/README.md index f46a2b074..c98013d05 100644 --- a/packages/map/README.md +++ b/packages/map/README.md @@ -12,22 +12,17 @@ npm install @packrat/map ```js -import { multiply } from '@packrat/map'; +import { Map } from '@packrat/map'; // ... - -const result = await multiply(3, 7); +return ( + // ... + + // ... +) ``` -## Contributing - -See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. - ## License MIT - ---- - -Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob) diff --git a/packages/map/package.json b/packages/map/package.json index 40bd1ea38..87a1ca87c 100755 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -6,25 +6,25 @@ "main": "src/index.tsx", "module:jsx": "src", "name": "@packrat/map", - "peerDependencies": { - "@expo/vector-icons": "*", + "dependencies": { + "@expo/vector-icons": "^14.0.0", "@packrat/config": "*", "@packrat/ui": "*", - "@react-native-community/geolocation": "*", - "@rnmapbox/maps": "*", - "@tmcw/togeojson": "*", - "expo-document-picker": "*", - "expo-file-system": "*", - "file-saver": "*", - "mapbox-gl": "*", + "@react-native-community/geolocation": "^3.0.6", + "@rnmapbox/maps": "^10.1.29", + "@tmcw/togeojson": "^5.7.0", + "expo-document-picker": "~11.10.1", + "expo-file-system": "~16.0.9", + "file-saver": "^2.0.5", + "mapbox-gl": "1.13.3", "react": "*", "react-native": "*", - "togpx": "*", - "xmldom": "*" + "togpx": "^0.5.4", + "xmldom": "^0.6.0" }, "private": true, "sideEffects": [ "*.css" ], "version": "0.0.1" -} +} \ No newline at end of file From ea2b7af90467fdd2d4703cd822d3675030f87c25 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Tue, 27 Aug 2024 12:18:37 +0100 Subject: [PATCH 006/127] =?UTF-8?q?=E2=9C=A8=20adapt=20modal=20to=20sheet?= =?UTF-8?q?=20on=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/src/modal/BaseModal.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/ui/src/modal/BaseModal.tsx b/packages/ui/src/modal/BaseModal.tsx index 94dc986f5..0284450a9 100644 --- a/packages/ui/src/modal/BaseModal.tsx +++ b/packages/ui/src/modal/BaseModal.tsx @@ -158,6 +158,25 @@ export const BaseModal = ({ + + + + + + + + + ); }; From d560ae0a48c14857f64debed58aa48b00f2a4b8b Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Wed, 4 Sep 2024 15:29:03 +0100 Subject: [PATCH 007/127] =?UTF-8?q?WIP(auth):=20=F0=9F=9A=A7=20generate=20?= =?UTF-8?q?access=20and=20refresh=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/controllers/user/userSignIn.ts | 17 ++++++++++--- server/src/db/schema.ts | 9 ++++++- server/src/drizzle/methods/User.ts | 31 ++++++++++++++++++----- server/worker-configuration.d.ts | 1 + 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/server/src/controllers/user/userSignIn.ts b/server/src/controllers/user/userSignIn.ts index 9386d8c05..9bacbb892 100644 --- a/server/src/controllers/user/userSignIn.ts +++ b/server/src/controllers/user/userSignIn.ts @@ -7,7 +7,7 @@ export const userSignIn = async (c) => { const { email, password } = await c.req.json(); const userClass = new User(); const user = await userClass.findByCredentials(email, password); - await userClass.generateAuthToken(c.env.JWT_SECRET, user.id); + await userClass.generateAccessToken(c.env.JWT_SECRET, user.id); return c.json({ user }, 200); } catch (error) { return c.json({ error: `Failed to sign in: ${error.message}` }, 500); @@ -21,9 +21,18 @@ export function userSignInRoute() { const userClass = new User(); const user = await userClass.findByCredentials(input.email, input.password); if (!user) { - throw new Error('User not found'); + throw new Error('Wrong email or password'); } - await userClass.generateAuthToken(env.JWT_SECRET, user.id); - return user; + + const accessToken = await userClass.generateAccessToken( + env.JWT_SECRET, + user.id, + ); + const refreshToken = await userClass.generateRefreshToken( + env.REFRESH_TOKEN_SECRET, + user.id, + ); + + return { accessToken, refreshToken }; }); } diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 4ff7ce3f2..d2749ec61 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -32,7 +32,6 @@ export const user = sqliteTable('user', { name: text('name').notNull(), password: text('password').notNull(), // Trim + MinLength(7) + Validation email: text('email').notNull().unique(), // Lowercase + Trim + Validation - token: text('token'), // Trim googleId: text('google_id'), code: text('code'), is_certified_guide: integer('is_certified_guide', { @@ -484,6 +483,14 @@ export const tripGeojsonsRelations = relations(tripGeojsons, ({ one }) => ({ }), })); +export const refreshTokens = sqliteTable('refresh_tokens', { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + useId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + token: text('token').notNull(), +}); + export type User = InferSelectModel; export type InsertUser = InferInsertModel; export const insertUserSchema = createInsertSchema(user); diff --git a/server/src/drizzle/methods/User.ts b/server/src/drizzle/methods/User.ts index 64d2541fc..340bedd73 100644 --- a/server/src/drizzle/methods/User.ts +++ b/server/src/drizzle/methods/User.ts @@ -4,6 +4,7 @@ import { DbClient } from '../../db/client'; import { type InsertUser, user as UserTable, + refreshTokens, userFavoritePacks, } from '../../db/schema'; import bcrypt from 'bcryptjs'; @@ -35,7 +36,7 @@ export class User { return DbClient.instance .select() .from(UserTable) - .where(eq(UserTable.role, "admin")) + .where(eq(UserTable.role, 'admin')) .limit(1) .get(); } @@ -82,15 +83,33 @@ export class User { } } - async generateAuthToken(jwtSecret: string, id: string) { + async generateAccessToken(jwtSecret: string, id: string) { if (!jwtSecret) throw new Error('jwtSecret is not defined'); try { - const token = await jwt.sign({ id }, jwtSecret); - const filter = eq(UserTable.id, id); - await DbClient.instance.update(UserTable).set({ token }).where(filter); + const token = await jwt.sign( + { id, exp: Math.floor(Date.now() / 1000) + 60 * 30 }, // 30 mins expiry + jwtSecret, + ); return token; } catch (error) { - throw new Error(`Failed to generate token: ${error.message}`); + throw new Error(`Failed to generate access token: ${error.message}`); + } + } + + async generateRefreshToken(jwtSecret: string, id: string) { + if (!jwtSecret) throw new Error('jwtSecret is not defined'); + try { + const token = await jwt.sign( + { id, exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 14 }, // 14 days expiry + jwtSecret, + ); + await DbClient.instance + .insert(refreshTokens) + .values({ token, userId: id }); + // TODO(current) + return token; + } catch (error) { + throw new Error(`Failed to generate refresh token: ${error.message}`); } } diff --git a/server/worker-configuration.d.ts b/server/worker-configuration.d.ts index 927e1cc9e..f366684ae 100644 --- a/server/worker-configuration.d.ts +++ b/server/worker-configuration.d.ts @@ -7,6 +7,7 @@ interface Env { STMP_EMAIL: 'test'; STMP_PASSWORD: 'test'; JWT_SECRET: 'test'; + REFRESH_TOKEN_SECRET: string; SEND_GRID_API_KEY: 'test'; MAPBOX_ACCESS_TOKEN: 'test'; OSM_URI: 'test'; From 6afa3afa9b33531fea0df6f8cef7bc529830d806 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Thu, 5 Sep 2024 06:29:43 +0100 Subject: [PATCH 008/127] =?UTF-8?q?WIP(auth):=20=F0=9F=90=9B=20fix=20typo'?= =?UTF-8?q?ed=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/db/schema.ts | 2 +- server/src/drizzle/methods/User.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index d2749ec61..6cdd13f1c 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -487,7 +487,7 @@ export const refreshTokens = sqliteTable('refresh_tokens', { id: text('id') .primaryKey() .$defaultFn(() => createId()), - useId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), token: text('token').notNull(), }); diff --git a/server/src/drizzle/methods/User.ts b/server/src/drizzle/methods/User.ts index 340bedd73..0e370ca55 100644 --- a/server/src/drizzle/methods/User.ts +++ b/server/src/drizzle/methods/User.ts @@ -106,7 +106,6 @@ export class User { await DbClient.instance .insert(refreshTokens) .values({ token, userId: id }); - // TODO(current) return token; } catch (error) { throw new Error(`Failed to generate refresh token: ${error.message}`); From 3ab51e33a4d48a700efcae2a937f6027042cd783 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Fri, 6 Sep 2024 08:04:15 +0100 Subject: [PATCH 009/127] =?UTF-8?q?=E2=9C=A8=20check=20auth=20solely=20in?= =?UTF-8?q?=20auth=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throwing authentication error in `createContext` (in `extractTokenAndGetUser`) means accessing public routes would also require valid auth token. This commit removes the the throwing of error from inside `createContext` and leaves it in `auth` middleware alone. This means only protected routes will check for authentication which is the desired flow. --- server/src/trpc/utils/auth.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/server/src/trpc/utils/auth.ts b/server/src/trpc/utils/auth.ts index 8520da6bb..75b9cadb2 100644 --- a/server/src/trpc/utils/auth.ts +++ b/server/src/trpc/utils/auth.ts @@ -1,5 +1,6 @@ import { TRPCError } from '@trpc/server'; -import { User } from '../../drizzle/methods/User'; +import { User as UserRepository } from '../../drizzle/methods/User'; +import { type User } from 'src/db/schema'; import * as jwt from 'hono/jwt'; // import * as jwt from 'hono/jwt'; @@ -30,31 +31,21 @@ const extractToken = (req: Request): string | null => { /** * Finds the user based on the verified token. - * @param {PrismaClient} prisma - The Prisma client. - * @param {JwtPayload} decoded - The decoded JWT payload. * @param {string} token - The JWT token. - * @returns {Promise} - The user associated with the token. - * @throws {Error} If user is not found. + * @param {string} jwtSecret - The JWT secret. + * @returns {Promise} - The user associated with the token. Resolves to null if token couldn't be verified or user is not found. */ -const findUser = async (token: string, jwtSecret: string) => { - const userClass = new User(); +const findUser = async (token: string, jwtSecret: string): Promise => { + const userRepository = new UserRepository(); + let user: User = null; // const user: any = await userClass.validateResetToken(token, jwtSecret); try { const decoded = await jwt.verify(token, jwtSecret); - const user = await userClass.findUser({ userId: decoded.id }); - if (!user) - throw new TRPCError({ - code: 'UNAUTHORIZED', - message: 'User associated with this token not found.', - }); - - return user; + user = await userRepository.findUser({ userId: decoded.id as string }); } catch { - throw new TRPCError({ - code: 'UNAUTHORIZED', - message: 'User associated with this token not found.', - }); + // pass } + return user; }; const extractTokenAndGetUser = async (req: Request, jwtSecret: string) => { From ba79268c36160e56e16eb719803573a3c7fc85c5 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Fri, 6 Sep 2024 09:33:52 +0100 Subject: [PATCH 010/127] =?UTF-8?q?WIP(auth:client):=20=F0=9F=9A=A7=20stor?= =?UTF-8?q?e=20access=20and=20refresh=20tokens=20on=20login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/modules/auth/hooks/useLogin.ts | 4 ++-- .../app/modules/auth/hooks/useSessionSignIn.ts | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/app/modules/auth/hooks/useLogin.ts b/packages/app/modules/auth/hooks/useLogin.ts index 14e9973ef..0d25e2b0f 100644 --- a/packages/app/modules/auth/hooks/useLogin.ts +++ b/packages/app/modules/auth/hooks/useLogin.ts @@ -19,8 +19,8 @@ export const useLogin = (): UseLoginReturn => { const handleLogin: UseLoginReturn['handleLogin'] = (data) => { const { email, password } = data; signIn({ email, password }) - .then((user) => { - sessionSignIn(user); + .then((tokens) => { + sessionSignIn(tokens); }) .catch(() => {}); }; diff --git a/packages/app/modules/auth/hooks/useSessionSignIn.ts b/packages/app/modules/auth/hooks/useSessionSignIn.ts index 2d622db21..e7e2ce25b 100644 --- a/packages/app/modules/auth/hooks/useSessionSignIn.ts +++ b/packages/app/modules/auth/hooks/useSessionSignIn.ts @@ -7,14 +7,12 @@ export const useSessionSignIn = () => { const setUser = useUserSetter(); const router = useRouter(); - const sessionSignIn = useCallback((user) => { - if (user?.token) { - (async () => { - setUser(user); - await Storage.setItem('token', user.token); - router.push('/'); - })(); - } + const sessionSignIn = useCallback((tokens) => { + (async () => { + await Storage.setItem('token', tokens.accessToken); + await Storage.setItem('refreshToken', tokens.refreshToken); + router.push('/'); + })(); }, []); return sessionSignIn; From fe08e647a732b6e20b175852c0432e72d87baeff Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Fri, 6 Sep 2024 09:36:40 +0100 Subject: [PATCH 011/127] =?UTF-8?q?WIP(auth:server):=20=F0=9F=9A=A7=20add?= =?UTF-8?q?=20migration=20for=20refreshTokens=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/migrations/0004_misty_gorilla_man.sql | 8 + server/migrations/meta/0004_snapshot.json | 1250 ++++++++++++++++++ server/migrations/meta/_journal.json | 7 + 3 files changed, 1265 insertions(+) create mode 100644 server/migrations/0004_misty_gorilla_man.sql create mode 100644 server/migrations/meta/0004_snapshot.json diff --git a/server/migrations/0004_misty_gorilla_man.sql b/server/migrations/0004_misty_gorilla_man.sql new file mode 100644 index 000000000..435bff98d --- /dev/null +++ b/server/migrations/0004_misty_gorilla_man.sql @@ -0,0 +1,8 @@ +CREATE TABLE `refresh_tokens` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text, + `token` text NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +ALTER TABLE `user` DROP COLUMN `token`; \ No newline at end of file diff --git a/server/migrations/meta/0004_snapshot.json b/server/migrations/meta/0004_snapshot.json new file mode 100644 index 000000000..980f5c16d --- /dev/null +++ b/server/migrations/meta/0004_snapshot.json @@ -0,0 +1,1250 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "25256b91-d058-4509-ab3a-a877c2ce278a", + "prevId": "57e2d274-a728-4d09-9aff-74d3b92399fa", + "tables": { + "conversation": { + "name": "conversation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "itemTypeId": { + "name": "itemTypeId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "history": { + "name": "history", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "geojson": { + "name": "geojson", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "geo_json_id": { + "name": "geo_json_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "properties": { + "name": "properties", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geometry": { + "name": "geometry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "global": { + "name": "global", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "item_category_id_item_category_id_fk": { + "name": "item_category_id_item_category_id_fk", + "tableFrom": "item", + "tableTo": "item_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "item_owner_id_user_id_fk": { + "name": "item_owner_id_user_id_fk", + "tableFrom": "item", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_category": { + "name": "item_category", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_owners": { + "name": "item_owners", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_owners_item_id_item_id_fk": { + "name": "item_owners_item_id_item_id_fk", + "tableFrom": "item_owners", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_owners_owner_id_user_id_fk": { + "name": "item_owners_owner_id_user_id_fk", + "tableFrom": "item_owners", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "owner_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "item_packs": { + "name": "item_packs", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_packs_item_id_item_id_fk": { + "name": "item_packs_item_id_item_id_fk", + "tableFrom": "item_packs", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_packs_pack_id_pack_id_fk": { + "name": "item_packs_pack_id_pack_id_fk", + "tableFrom": "item_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "pack_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "node": { + "name": "node", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lat": { + "name": "lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lon": { + "name": "lon", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pack": { + "name": "pack", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "grades": { + "name": "grades", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weight\":\"\",\"essentialItems\":\"\",\"redundancyAndVersatility\":\"\"}'" + }, + "scores": { + "name": "scores", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weightScore\":0,\"essentialItemsScore\":0,\"redundancyAndVersatilityScore\":0}'" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'pack'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "pack_owner_id_user_id_fk": { + "name": "pack_owner_id_user_id_fk", + "tableFrom": "pack", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "refresh_tokens": { + "name": "refresh_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_tokens_user_id_user_id_fk": { + "name": "refresh_tokens_user_id_user_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "relation": { + "name": "relation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'relation'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "template": { + "name": "template", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pack'" + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_global_template": { + "name": "is_global_template", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "template_created_by_user_id_fk": { + "name": "template_created_by_user_id_fk", + "tableFrom": "template", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip": { + "name": "trip", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "weather": { + "name": "weather", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "packs_id": { + "name": "packs_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'trip'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "trip_owner_id_user_id_fk": { + "name": "trip_owner_id_user_id_fk", + "tableFrom": "trip", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trip_packs_id_pack_id_fk": { + "name": "trip_packs_id_pack_id_fk", + "tableFrom": "trip", + "tableTo": "pack", + "columnsFrom": [ + "packs_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip_geojsons": { + "name": "trip_geojsons", + "columns": { + "trip_id": { + "name": "trip_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geojson_id": { + "name": "geojson_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "trip_geojsons_trip_id_trip_id_fk": { + "name": "trip_geojsons_trip_id_trip_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "trip", + "columnsFrom": [ + "trip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "trip_geojsons_geojson_id_geojson_id_fk": { + "name": "trip_geojsons_geojson_id_geojson_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "geojson", + "columnsFrom": [ + "geojson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "geojson_id", + "trip_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_certified_guide": { + "name": "is_certified_guide", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token": { + "name": "password_reset_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token_expiration": { + "name": "password_reset_token_expiration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "offline_maps": { + "name": "offline_maps", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_image": { + "name": "profile_image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preferred_weather": { + "name": "preferred_weather", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'celsius'" + }, + "preferred_weight": { + "name": "preferred_weight", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'lb'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_favorite_packs": { + "name": "user_favorite_packs", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorite_packs_user_id_user_id_fk": { + "name": "user_favorite_packs_user_id_user_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_favorite_packs_pack_id_pack_id_fk": { + "name": "user_favorite_packs_pack_id_pack_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "pack_id", + "user_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "way": { + "name": "way", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "way_nodes": { + "name": "way_nodes", + "columns": { + "way_id": { + "name": "way_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "way_nodes_way_id_way_id_fk": { + "name": "way_nodes_way_id_way_id_fk", + "tableFrom": "way_nodes", + "tableTo": "way", + "columnsFrom": [ + "way_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "way_nodes_node_id_node_id_fk": { + "name": "way_nodes_node_id_node_id_fk", + "tableFrom": "way_nodes", + "tableTo": "node", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "node_id", + "way_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/server/migrations/meta/_journal.json b/server/migrations/meta/_journal.json index 9e5b092be..9f0e82e18 100644 --- a/server/migrations/meta/_journal.json +++ b/server/migrations/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1721509132975, "tag": "0003_reflective_mimic", "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1725521686790, + "tag": "0004_misty_gorilla_man", + "breakpoints": true } ] } \ No newline at end of file From 631b872f410663890805f99f3cf1ec31149c6990 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Fri, 6 Sep 2024 09:54:23 +0100 Subject: [PATCH 012/127] =?UTF-8?q?WIP(auth):=20=F0=9F=9A=A7=20add=20token?= =?UTF-8?q?=20refreshing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/config/trpcAxiosClient.ts | 45 +++++++++++++++---- packages/app/trpc.ts | 9 +++- server/src/controllers/auth/index.ts | 1 + server/src/controllers/auth/refreshToken.ts | 21 +++++++++ server/src/drizzle/methods/User.ts | 6 +++ server/src/routes/trpcRouter.ts | 2 + .../src/services/user/refreshTokenService.ts | 24 ++++++++++ server/src/services/user/user.service.ts | 1 + 8 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 server/src/controllers/auth/refreshToken.ts create mode 100644 server/src/services/user/refreshTokenService.ts diff --git a/packages/app/config/trpcAxiosClient.ts b/packages/app/config/trpcAxiosClient.ts index 6eed78e04..f1f74a869 100644 --- a/packages/app/config/trpcAxiosClient.ts +++ b/packages/app/config/trpcAxiosClient.ts @@ -1,7 +1,10 @@ -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosResponse } from 'axios'; import { InformUser } from 'app/utils/ToastUtils'; import { logoutAuthUser } from 'app/utils/userUtils'; import { getErrorMessageFromError } from 'app/utils/apiUtils'; +import { Storage } from 'app/utils/storage'; +import { vanillaTrpcClient } from 'app/trpc'; +import { TRPCErrorResponse } from '@trpc/server/rpc'; const REQUESTS_TO_SKIP_SUCCESS_MESSAGE = [ 'getMe', @@ -35,21 +38,45 @@ const responseInterceptor = (response: AxiosResponse) => { return response; }; -const responseErrorInterceptor = (response: AxiosResponse) => { - if (response?.response?.data?.error?.data?.httpStatus === 401) { - logoutAuthUser(); +const responseErrorInterceptor = async ( + error: AxiosError, +) => { + console.log('error!!!'); + console.log( + 'response?.response?.data?.error[0]?.data?.httpStatus', + error?.response?.data?.error?.data?.httpStatus, + ); + if (error?.response?.data[0]?.error?.data?.httpStatus === 401) { + // TODO: handle non batch links + const refreshToken = await Storage.getItem('refreshToken'); + + if (!refreshToken) return; // user is logged out if refreshToken isn't present + + // maybe token expired? try refreshing. + try { + const tokens = await vanillaTrpcClient.refreshToken.query(refreshToken); + await Storage.setItem('token', tokens.accessToken); + await Storage.setItem('refreshToken', tokens.refreshToken); + + // rety request + error.config.headers.Authorization = 'Bearer ' + tokens.accessToken; + return await axios.request(error.config); + } catch { + // refreshToken has probably also expired. logout user. + logoutAuthUser(); + } } if ( - response.config.method === 'get' || + error.config.method === 'get' || REQUESTS_TO_SKIP_ERROR_MESSAGE.some((url) => - response.config.url?.includes?.(url), + error.config.url?.includes?.(url), ) ) { - return response; + return error; } - const responseMessage = getErrorMessageFromError(response); + const responseMessage = getErrorMessageFromError(error); if (responseMessage) { InformUser({ @@ -60,7 +87,7 @@ const responseErrorInterceptor = (response: AxiosResponse) => { }); } - return response; + return error; }; axiosInstance.interceptors.response.use( diff --git a/packages/app/trpc.ts b/packages/app/trpc.ts index cbf53c24f..cd8b3a4e0 100644 --- a/packages/app/trpc.ts +++ b/packages/app/trpc.ts @@ -36,7 +36,7 @@ const axiosFetch = async (url, options) => { // export const reactTrpc = createTRPCReact(); export const queryTrpc = createTRPCReact(); -export const trpc = queryTrpc.createClient({ +const trpcClientOpts = { links: [ httpBatchLink({ url: `${api}/trpc`, @@ -50,6 +50,11 @@ export const trpc = queryTrpc.createClient({ }), ], transformer: undefined, -}); +}; + +export const trpc = queryTrpc.createClient(trpcClientOpts); + +export const vanillaTrpcClient = + createTRPCProxyClient(trpcClientOpts); // For calling procedures imperatively (outside of a component) export const queryClient = new QueryClient(); diff --git a/server/src/controllers/auth/index.ts b/server/src/controllers/auth/index.ts index c77341738..e9107eb05 100644 --- a/server/src/controllers/auth/index.ts +++ b/server/src/controllers/auth/index.ts @@ -1,3 +1,4 @@ export * from './checkCode'; export * from './emailExists'; export * from './updatePassword'; +export * from './refreshToken'; diff --git a/server/src/controllers/auth/refreshToken.ts b/server/src/controllers/auth/refreshToken.ts new file mode 100644 index 000000000..c0ecc466a --- /dev/null +++ b/server/src/controllers/auth/refreshToken.ts @@ -0,0 +1,21 @@ +import { protectedProcedure, publicProcedure } from 'src/trpc'; +import { z } from 'zod'; +import { refreshTokenService } from 'src/services/user/user.service'; +import { TRPCError } from '@trpc/server'; + +export function refreshTokenRoute() { + return publicProcedure.input(z.string().min(1)).query(async (opts) => { + try { + return await refreshTokenService( + opts.ctx.env.JWT_SECRET, + opts.ctx.env.REFRESH_TOKEN_SECRET, + opts.input, + ); + } catch (error) { + // Refresh token expires + throw new TRPCError({ + code: 'UNAUTHORIZED', + }); + } + }); +} diff --git a/server/src/drizzle/methods/User.ts b/server/src/drizzle/methods/User.ts index 0e370ca55..2175540b7 100644 --- a/server/src/drizzle/methods/User.ts +++ b/server/src/drizzle/methods/User.ts @@ -112,6 +112,12 @@ export class User { } } + async deleteRefreshToken(token: string) { + await DbClient.instance + .delete(refreshTokens) + .where(eq(refreshTokens.token, token)); + } + async generateResetToken( jwtSecret: string, clientUrl: string, diff --git a/server/src/routes/trpcRouter.ts b/server/src/routes/trpcRouter.ts index e399ec51c..d6dcfc319 100644 --- a/server/src/routes/trpcRouter.ts +++ b/server/src/routes/trpcRouter.ts @@ -18,6 +18,7 @@ import { checkCodeRoute, emailExistsRoute, updatePasswordRoute, + refreshTokenRoute, } from '../controllers/auth'; import { getWeatherRoute } from '../controllers/weather'; import { @@ -96,6 +97,7 @@ export const appRouter = trpcRouter({ getUserById: getUserByIdRoute(), signIn: userSignInRoute(), signUp: signUpRoute(), + refreshToken: refreshTokenRoute(), resetPassword: resetPasswordRoute(), getGoogleAuthURL: getGoogleAuthURLRoute(), googleSignin: googleSigninRoute(), diff --git a/server/src/services/user/refreshTokenService.ts b/server/src/services/user/refreshTokenService.ts new file mode 100644 index 000000000..5d45e5edb --- /dev/null +++ b/server/src/services/user/refreshTokenService.ts @@ -0,0 +1,24 @@ +import * as jwt from 'hono/jwt'; +import { User as UserRepository } from 'src/drizzle/methods/User'; + +export const refreshTokenService = async ( + jwtSecret: string, + refreshTokenSecret: string, + token: string, +) => { + const { id: userId } = await jwt.verify(token, refreshTokenSecret); // also checks expiry + + const userRepository = new UserRepository(); + + const accessToken = await userRepository.generateAccessToken( + jwtSecret, + userId as string, + ); + const refreshToken = await userRepository.generateRefreshToken( + refreshTokenSecret, + userId as string, + ); + await userRepository.deleteRefreshToken(token); // revoke former refreshToken + + return { accessToken, refreshToken }; +}; diff --git a/server/src/services/user/user.service.ts b/server/src/services/user/user.service.ts index a6007d138..2c1b52b13 100644 --- a/server/src/services/user/user.service.ts +++ b/server/src/services/user/user.service.ts @@ -2,3 +2,4 @@ export * from './findUserAndUpdate'; export * from './findUserByEmail'; export * from './addToFavoritesService'; export * from './getUserByIdService'; +export * from './refreshTokenService'; From 4625e219ea4b75457143f05e9bb90aedf9d56ff9 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Fri, 6 Sep 2024 18:17:51 +0100 Subject: [PATCH 013/127] =?UTF-8?q?WIP(auth):=20=F0=9F=9A=A7=20handle=20lo?= =?UTF-8?q?gout=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/config/trpcAxiosClient.ts | 8 ++------ packages/app/modules/auth/hooks/useLogout.ts | 12 ++++++++++-- packages/app/trpc.ts | 9 +++++++-- packages/app/utils/userUtils.ts | 8 +++++--- server/src/controllers/auth/index.ts | 1 + server/src/controllers/auth/logout.ts | 14 ++++++++++++++ server/src/controllers/auth/refreshToken.ts | 4 ++-- server/src/routes/trpcRouter.ts | 2 ++ server/src/services/auth/auth.service.ts | 2 ++ server/src/services/auth/logoutService.ts | 7 +++++++ .../services/{user => auth}/refreshTokenService.ts | 0 server/src/services/user/user.service.ts | 1 - 12 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 server/src/controllers/auth/logout.ts create mode 100644 server/src/services/auth/auth.service.ts create mode 100644 server/src/services/auth/logoutService.ts rename server/src/services/{user => auth}/refreshTokenService.ts (100%) diff --git a/packages/app/config/trpcAxiosClient.ts b/packages/app/config/trpcAxiosClient.ts index f1f74a869..c34a85a67 100644 --- a/packages/app/config/trpcAxiosClient.ts +++ b/packages/app/config/trpcAxiosClient.ts @@ -41,11 +41,6 @@ const responseInterceptor = (response: AxiosResponse) => { const responseErrorInterceptor = async ( error: AxiosError, ) => { - console.log('error!!!'); - console.log( - 'response?.response?.data?.error[0]?.data?.httpStatus', - error?.response?.data?.error?.data?.httpStatus, - ); if (error?.response?.data[0]?.error?.data?.httpStatus === 401) { // TODO: handle non batch links const refreshToken = await Storage.getItem('refreshToken'); @@ -61,9 +56,10 @@ const responseErrorInterceptor = async ( // rety request error.config.headers.Authorization = 'Bearer ' + tokens.accessToken; return await axios.request(error.config); - } catch { + } catch (error) { // refreshToken has probably also expired. logout user. logoutAuthUser(); + return error; } } diff --git a/packages/app/modules/auth/hooks/useLogout.ts b/packages/app/modules/auth/hooks/useLogout.ts index d97bd4db4..02fd19d8d 100644 --- a/packages/app/modules/auth/hooks/useLogout.ts +++ b/packages/app/modules/auth/hooks/useLogout.ts @@ -1,12 +1,20 @@ import { Storage } from 'app/utils/storage'; import { useUserSetter } from './useUserSetter'; +import { queryTrpc } from 'app/trpc'; export const useLogout = () => { const setUser = useUserSetter(); + const utils = queryTrpc.useUtils(); - const logout = () => { + const logout = async () => { setUser(null); - Storage.removeItem('token'); + try { + await utils.logout.fetch(await Storage.getItem('refreshToken')); + } catch { + // pass + } + await Storage.removeItem('token'); + await Storage.removeItem('refreshToken'); }; return logout; diff --git a/packages/app/trpc.ts b/packages/app/trpc.ts index cd8b3a4e0..658b8eeed 100644 --- a/packages/app/trpc.ts +++ b/packages/app/trpc.ts @@ -54,7 +54,12 @@ const trpcClientOpts = { export const trpc = queryTrpc.createClient(trpcClientOpts); -export const vanillaTrpcClient = - createTRPCProxyClient(trpcClientOpts); // For calling procedures imperatively (outside of a component) +export const vanillaTrpcClient = createTRPCProxyClient({ + links: [ + httpBatchLink({ + url: `${api}/trpc`, + }), + ], +}); // For calling procedures imperatively (outside of a component) export const queryClient = new QueryClient(); diff --git a/packages/app/utils/userUtils.ts b/packages/app/utils/userUtils.ts index e18847d7f..cb88f7ab1 100644 --- a/packages/app/utils/userUtils.ts +++ b/packages/app/utils/userUtils.ts @@ -1,9 +1,11 @@ import { Storage } from 'app/utils/storage'; import { getQueryKey } from '@trpc/react-query'; -import { queryClient, queryTrpc } from 'app/trpc'; +import { queryClient, queryTrpc, vanillaTrpcClient } from 'app/trpc'; -export const logoutAuthUser = () => { - Storage.removeItem('token'); +export const logoutAuthUser = async () => { + await vanillaTrpcClient.logout.query(await Storage.getItem('refreshToken')); + await Storage.removeItem('token'); + await Storage.removeItem('refreshToken'); queryClient.setQueriesData( getQueryKey(queryTrpc.getMe, undefined, 'query'), null, diff --git a/server/src/controllers/auth/index.ts b/server/src/controllers/auth/index.ts index e9107eb05..7b2837a43 100644 --- a/server/src/controllers/auth/index.ts +++ b/server/src/controllers/auth/index.ts @@ -2,3 +2,4 @@ export * from './checkCode'; export * from './emailExists'; export * from './updatePassword'; export * from './refreshToken'; +export * from './logout'; diff --git a/server/src/controllers/auth/logout.ts b/server/src/controllers/auth/logout.ts new file mode 100644 index 000000000..f2223e6ad --- /dev/null +++ b/server/src/controllers/auth/logout.ts @@ -0,0 +1,14 @@ +import { publicProcedure } from 'src/trpc'; +import { z } from 'zod'; +import { logoutService as logout } from 'src/services/auth/auth.service'; +import { TRPCError } from '@trpc/server'; + +export function logoutRoute() { + return publicProcedure.input(z.string().min(1)).query(async (opts) => { + try { + return await logout(opts.input); + } catch { + throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR' }); + } + }); +} diff --git a/server/src/controllers/auth/refreshToken.ts b/server/src/controllers/auth/refreshToken.ts index c0ecc466a..b44b8c3a9 100644 --- a/server/src/controllers/auth/refreshToken.ts +++ b/server/src/controllers/auth/refreshToken.ts @@ -1,6 +1,6 @@ -import { protectedProcedure, publicProcedure } from 'src/trpc'; +import { publicProcedure } from 'src/trpc'; import { z } from 'zod'; -import { refreshTokenService } from 'src/services/user/user.service'; +import { refreshTokenService } from 'src/services/auth/auth.service'; import { TRPCError } from '@trpc/server'; export function refreshTokenRoute() { diff --git a/server/src/routes/trpcRouter.ts b/server/src/routes/trpcRouter.ts index d6dcfc319..3fddd97c8 100644 --- a/server/src/routes/trpcRouter.ts +++ b/server/src/routes/trpcRouter.ts @@ -19,6 +19,7 @@ import { emailExistsRoute, updatePasswordRoute, refreshTokenRoute, + logoutRoute, } from '../controllers/auth'; import { getWeatherRoute } from '../controllers/weather'; import { @@ -97,6 +98,7 @@ export const appRouter = trpcRouter({ getUserById: getUserByIdRoute(), signIn: userSignInRoute(), signUp: signUpRoute(), + logout: logoutRoute(), refreshToken: refreshTokenRoute(), resetPassword: resetPasswordRoute(), getGoogleAuthURL: getGoogleAuthURLRoute(), diff --git a/server/src/services/auth/auth.service.ts b/server/src/services/auth/auth.service.ts new file mode 100644 index 000000000..eed20bf3f --- /dev/null +++ b/server/src/services/auth/auth.service.ts @@ -0,0 +1,2 @@ +export * from './refreshTokenService'; +export * from './logoutService'; diff --git a/server/src/services/auth/logoutService.ts b/server/src/services/auth/logoutService.ts new file mode 100644 index 000000000..fc5a247ff --- /dev/null +++ b/server/src/services/auth/logoutService.ts @@ -0,0 +1,7 @@ +import { User as UserRepository } from 'src/drizzle/methods/User'; + +export const logoutService = async (refreshToken: string) => { + const userRepository = new UserRepository(); + + await userRepository.deleteRefreshToken(refreshToken); +}; diff --git a/server/src/services/user/refreshTokenService.ts b/server/src/services/auth/refreshTokenService.ts similarity index 100% rename from server/src/services/user/refreshTokenService.ts rename to server/src/services/auth/refreshTokenService.ts diff --git a/server/src/services/user/user.service.ts b/server/src/services/user/user.service.ts index 2c1b52b13..a6007d138 100644 --- a/server/src/services/user/user.service.ts +++ b/server/src/services/user/user.service.ts @@ -2,4 +2,3 @@ export * from './findUserAndUpdate'; export * from './findUserByEmail'; export * from './addToFavoritesService'; export * from './getUserByIdService'; -export * from './refreshTokenService'; From 59c664123a7f12624421a2e4fa206c365137453d Mon Sep 17 00:00:00 2001 From: Taron Date: Sat, 7 Sep 2024 01:24:55 +0400 Subject: [PATCH 014/127] feed cards refactor and ui changes --- packages/app/hooks/favorites/index.ts | 2 - .../CreatedAtLabel/CreatedAtLabel.tsx | 18 ++ .../feed/components/CreatedAtLabel/index.ts | 1 + .../FavoriteButton/FavoriteButton.tsx | 28 ++ .../feed/components/FavoriteButton/index.ts | 1 + .../components/{ => FeedCard}/FeedCard.tsx | 98 +++---- .../modules/feed/components/FeedCard/index.ts | 1 + .../modules/feed/components/FeedCard/utils.ts | 40 +++ packages/app/modules/feed/components/index.ts | 2 + packages/app/modules/feed/hooks/index.ts | 2 + .../feed/hooks}/useAddFavorite.ts | 2 +- .../feed/hooks}/useFetchUserFavorites.ts | 2 +- packages/app/modules/feed/index.ts | 11 +- packages/app/modules/feed/model.ts | 39 +++ .../app/modules/feed/screens/FeedScreen.tsx | 9 +- .../feed/widgets/FeedPreview/FeedPreview.tsx | 9 +- .../widgets/FeedPreview/FeedPreviewCard.tsx | 240 ------------------ .../pack/components/PackCard/PackCard.tsx | 18 ++ .../pack/components/PackCard/PackImage.tsx | 29 +++ .../components/PackCard/PackPrimaryCard.tsx | 59 +++++ .../components/PackCard/PackSecondaryCard.tsx | 38 +++ .../modules/pack/components/PackCard/index.ts | 1 + packages/app/modules/pack/components/index.ts | 1 + packages/app/modules/pack/index.ts | 1 + packages/app/modules/pack/model.ts | 5 + .../modules/user/components/UserDataCard.tsx | 2 +- .../user/components/UserDetailList.tsx | 20 +- packages/app/modules/user/hooks/useProfile.ts | 2 +- packages/ui/src/Details/Details.tsx | 49 ++++ packages/ui/src/Details/index.ts | 1 + packages/ui/src/card/Card.tsx | 23 ++ packages/ui/src/card/PrimaryCard.tsx | 53 ++++ packages/ui/src/card/SecondaryCard.tsx | 54 ++++ packages/ui/src/card/index.ts | 2 + packages/ui/src/card/model.ts | 13 + packages/ui/src/index.tsx | 4 +- 36 files changed, 579 insertions(+), 301 deletions(-) delete mode 100644 packages/app/hooks/favorites/index.ts create mode 100644 packages/app/modules/feed/components/CreatedAtLabel/CreatedAtLabel.tsx create mode 100644 packages/app/modules/feed/components/CreatedAtLabel/index.ts create mode 100644 packages/app/modules/feed/components/FavoriteButton/FavoriteButton.tsx create mode 100644 packages/app/modules/feed/components/FavoriteButton/index.ts rename packages/app/modules/feed/components/{ => FeedCard}/FeedCard.tsx (84%) create mode 100644 packages/app/modules/feed/components/FeedCard/index.ts create mode 100644 packages/app/modules/feed/components/FeedCard/utils.ts rename packages/app/{hooks/favorites => modules/feed/hooks}/useAddFavorite.ts (95%) rename packages/app/{hooks/favorites => modules/feed/hooks}/useFetchUserFavorites.ts (90%) create mode 100644 packages/app/modules/feed/model.ts delete mode 100644 packages/app/modules/feed/widgets/FeedPreview/FeedPreviewCard.tsx create mode 100644 packages/app/modules/pack/components/PackCard/PackCard.tsx create mode 100644 packages/app/modules/pack/components/PackCard/PackImage.tsx create mode 100644 packages/app/modules/pack/components/PackCard/PackPrimaryCard.tsx create mode 100644 packages/app/modules/pack/components/PackCard/PackSecondaryCard.tsx create mode 100644 packages/app/modules/pack/components/PackCard/index.ts create mode 100644 packages/app/modules/pack/model.ts create mode 100644 packages/ui/src/Details/Details.tsx create mode 100644 packages/ui/src/Details/index.ts create mode 100644 packages/ui/src/card/Card.tsx create mode 100644 packages/ui/src/card/PrimaryCard.tsx create mode 100644 packages/ui/src/card/SecondaryCard.tsx create mode 100644 packages/ui/src/card/index.ts create mode 100644 packages/ui/src/card/model.ts diff --git a/packages/app/hooks/favorites/index.ts b/packages/app/hooks/favorites/index.ts deleted file mode 100644 index e883e1944..000000000 --- a/packages/app/hooks/favorites/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './useFetchUserFavorites'; -export * from './useAddFavorite'; diff --git a/packages/app/modules/feed/components/CreatedAtLabel/CreatedAtLabel.tsx b/packages/app/modules/feed/components/CreatedAtLabel/CreatedAtLabel.tsx new file mode 100644 index 000000000..3dba940d0 --- /dev/null +++ b/packages/app/modules/feed/components/CreatedAtLabel/CreatedAtLabel.tsx @@ -0,0 +1,18 @@ +import { XStack, RText } from '@packrat/ui'; +import { Clock } from '@tamagui/lucide-icons'; +import React, { type FC } from 'react'; + +interface CreatedAtLabelProps { + date: string; +} +export const CreatedAtLabel: FC = ({ date }) => { + return ( + + + {date} + + ); +}; diff --git a/packages/app/modules/feed/components/CreatedAtLabel/index.ts b/packages/app/modules/feed/components/CreatedAtLabel/index.ts new file mode 100644 index 000000000..81212b482 --- /dev/null +++ b/packages/app/modules/feed/components/CreatedAtLabel/index.ts @@ -0,0 +1 @@ +export { CreatedAtLabel } from './CreatedAtLabel'; diff --git a/packages/app/modules/feed/components/FavoriteButton/FavoriteButton.tsx b/packages/app/modules/feed/components/FavoriteButton/FavoriteButton.tsx new file mode 100644 index 000000000..ed56821eb --- /dev/null +++ b/packages/app/modules/feed/components/FavoriteButton/FavoriteButton.tsx @@ -0,0 +1,28 @@ +import { AntDesign } from '@expo/vector-icons'; +import { RText, XStack } from '@packrat/ui'; +import React, { type FC } from 'react'; +import { Pressable, type GestureResponderEvent } from 'react-native'; + +interface FavoriteButtonProps { + isAuthUserFavorite: boolean; + onClick: (e: GestureResponderEvent) => void; + count: number; +} +export const FavoriteButton: FC = ({ + onClick, + isAuthUserFavorite, + count, +}) => { + return ( + + + + + {count} + + ); +}; diff --git a/packages/app/modules/feed/components/FavoriteButton/index.ts b/packages/app/modules/feed/components/FavoriteButton/index.ts new file mode 100644 index 000000000..5647cda88 --- /dev/null +++ b/packages/app/modules/feed/components/FavoriteButton/index.ts @@ -0,0 +1 @@ +export { FavoriteButton } from './FavoriteButton'; diff --git a/packages/app/modules/feed/components/FeedCard.tsx b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx similarity index 84% rename from packages/app/modules/feed/components/FeedCard.tsx rename to packages/app/modules/feed/components/FeedCard/FeedCard.tsx index 93c4426aa..5374ff139 100644 --- a/packages/app/modules/feed/components/FeedCard.tsx +++ b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx @@ -1,49 +1,60 @@ -import { AntDesign } from '@expo/vector-icons'; -import { formatDistanceToNow } from 'date-fns'; -import { MaterialIcons, Entypo } from '@expo/vector-icons'; -import useTheme from 'app/hooks/useTheme'; -import { TouchableOpacity } from 'react-native'; -import { DuplicateIcon } from 'app/assets/icons'; -import { truncateString } from 'app/utils/truncateString'; -import { RLink, RText as OriginalRText, ContextMenu } from '@packrat/ui'; -import { formatNumber } from 'app/utils/formatNumber'; -import { useAddFavorite } from 'app/hooks/favorites'; +import React, { type FC } from 'react'; +import { type FeedItem, type FeedType } from 'app/modules/feed/model'; +import { feedItemPackCardConverter } from './utils'; +import { PackCard } from 'app/modules/pack'; +import { type CardType } from '@packrat/ui'; +import { useAddFavorite } from 'app/modules/feed'; import { useAuthUser } from 'app/modules/auth'; -import { useRouter } from 'app/hooks/router'; -import { useItemWeightUnit } from 'app/modules/item'; -import { convertWeight } from 'app/utils/convertWeight'; -import Layout from 'app/components/layout/Layout'; -import { Button, Card, H2, Paragraph, XStack, YStack } from 'tamagui'; - -const RText: any = OriginalRText; - -interface CardProps { - type: string; - id: string; - owner: { - id: string; - username: string; - }; - name: string; - total_weight: number; - is_public: boolean; - favorited_by: Array<{ - id: string; - }>; - favorites_count: number; - owner_id: string | { id: string }; - destination: string; - createdAt: string; - owners: Array<{ any: any }>; - duration: string; - itemPacks?: any[]; -} -interface User { - id: string; +const convertersByType = { + pack: feedItemPackCardConverter, +}; + +const cardComponentsByType = { + pack: PackCard, +}; + +interface FeedCardProps { + feedType: FeedType; + cardType: CardType; + item: FeedItem; } -export function FeedCard({ +export const FeedCard: FC = ({ item, cardType, feedType }) => { + const { addFavorite } = useAddFavorite(); + const user = useAuthUser(); + const cardProps = + typeof convertersByType[feedType] === 'function' + ? convertersByType[feedType](item, user?.id) + : null; + + const handleAddToFavorite = () => { + if (!user) return; + const data = { + packId: item.id, + userId: user.id, + }; + + addFavorite(data); + }; + + if (!cardProps) { + return null; + } + + const CardComponent = cardComponentsByType[feedType]; + + return ( + + ); +}; + +/* +export function FeedCardOld({ type, id, owner, @@ -58,7 +69,7 @@ export function FeedCard({ owners, duration, itemPacks, -}: CardProps) { +}: FeedCardProps) { console.log('CardProps:', favorited_by); const user = useAuthUser(); const { currentTheme } = useTheme(); @@ -246,3 +257,4 @@ export function FeedCard({ ); } +*/ diff --git a/packages/app/modules/feed/components/FeedCard/index.ts b/packages/app/modules/feed/components/FeedCard/index.ts new file mode 100644 index 000000000..a6871b085 --- /dev/null +++ b/packages/app/modules/feed/components/FeedCard/index.ts @@ -0,0 +1 @@ +export { FeedCard } from './FeedCard'; diff --git a/packages/app/modules/feed/components/FeedCard/utils.ts b/packages/app/modules/feed/components/FeedCard/utils.ts new file mode 100644 index 000000000..ea18d5b94 --- /dev/null +++ b/packages/app/modules/feed/components/FeedCard/utils.ts @@ -0,0 +1,40 @@ +import { type FeedCardProps, type FeedItem } from 'modules/feed/model'; +import { formatDistanceToNowStrict } from 'date-fns'; +import { type PackDetails } from 'app/modules/pack'; +import { truncateString } from 'app/utils/truncateString'; + +type Converter = ( + input: Input, + currentUserId?: string | number, +) => Result; + +export const feedItemPackCardConverter: Converter< + FeedItem, + Omit, 'cardType' | 'toggleFavorite'> +> = (input, currentUserId) => { + return { + id: input.id, + createdAt: formatDistanceToNowStrict(new Date(input.createdAt), { + addSuffix: false, + }), + title: truncateString(input.name, 25), + ownerId: + typeof input.owner_id === 'string' + ? input.owner_id + : input.owner_id?.id || '', + details: { + score: input.total_score, + weight: input.total_weight, + quantity: + input?.itemPacks?.reduce( + (accumulator, currentValue) => + accumulator + currentValue?.item?.quantity, + 0, + ) ?? 0, + }, + isUserFavorite: input?.userFavoritePacks?.some( + (obj) => obj?.userId === currentUserId, + ), + favoriteCount: input.favorites_count, + }; +}; diff --git a/packages/app/modules/feed/components/index.ts b/packages/app/modules/feed/components/index.ts index f2a5b6056..3c0d35173 100644 --- a/packages/app/modules/feed/components/index.ts +++ b/packages/app/modules/feed/components/index.ts @@ -1,3 +1,5 @@ export { FeedCard } from './FeedCard'; export { FeedSearchFilter } from './FeedSearchFilter'; export { SearchProvider, SearchContext } from './SearchProvider'; +export { FavoriteButton } from './FavoriteButton'; +export { CreatedAtLabel } from './CreatedAtLabel'; diff --git a/packages/app/modules/feed/hooks/index.ts b/packages/app/modules/feed/hooks/index.ts index a1ff51b2d..096f0b3da 100644 --- a/packages/app/modules/feed/hooks/index.ts +++ b/packages/app/modules/feed/hooks/index.ts @@ -1 +1,3 @@ export { useFeed } from './useFeed'; +export { useAddFavorite } from './useAddFavorite'; +export { useFetchUserFavorites } from './useFetchUserFavorites'; diff --git a/packages/app/hooks/favorites/useAddFavorite.ts b/packages/app/modules/feed/hooks/useAddFavorite.ts similarity index 95% rename from packages/app/hooks/favorites/useAddFavorite.ts rename to packages/app/modules/feed/hooks/useAddFavorite.ts index 41b5af9d8..b52c8796f 100644 --- a/packages/app/hooks/favorites/useAddFavorite.ts +++ b/packages/app/modules/feed/hooks/useAddFavorite.ts @@ -1,4 +1,4 @@ -import { queryTrpc } from '../../trpc'; +import { queryTrpc } from 'app/trpc'; export function useAddFavorite() { const utils = queryTrpc.useContext(); diff --git a/packages/app/hooks/favorites/useFetchUserFavorites.ts b/packages/app/modules/feed/hooks/useFetchUserFavorites.ts similarity index 90% rename from packages/app/hooks/favorites/useFetchUserFavorites.ts rename to packages/app/modules/feed/hooks/useFetchUserFavorites.ts index cfb3e6c39..978fc6bff 100644 --- a/packages/app/hooks/favorites/useFetchUserFavorites.ts +++ b/packages/app/modules/feed/hooks/useFetchUserFavorites.ts @@ -1,4 +1,4 @@ -import { queryTrpc } from '../../trpc'; +import { queryTrpc } from 'app/trpc'; export const useFetchUserFavorites = (userId: string) => { const enabled = !!userId; diff --git a/packages/app/modules/feed/index.ts b/packages/app/modules/feed/index.ts index 94d9e2c14..1b1318e65 100644 --- a/packages/app/modules/feed/index.ts +++ b/packages/app/modules/feed/index.ts @@ -1,4 +1,11 @@ export * from './widgets'; export * from './screens'; -export { useFeed } from './hooks'; -export { SearchProvider, FeedSearchFilter, FeedCard } from './components'; +export { useFeed, useAddFavorite, useFetchUserFavorites } from './hooks'; +export { + SearchProvider, + FeedSearchFilter, + FeedCard, + FavoriteButton, + CreatedAtLabel, +} from './components'; +export { type FeedCardProps, type FeedItem } from './model'; diff --git a/packages/app/modules/feed/model.ts b/packages/app/modules/feed/model.ts new file mode 100644 index 000000000..cc30e1659 --- /dev/null +++ b/packages/app/modules/feed/model.ts @@ -0,0 +1,39 @@ +import { type CardType } from '@packrat/ui'; + +export type FeedType = 'pack'; + +export interface FeedItem { + id: string; + owner: { + id: string; + username: string; + }; + name: string; + total_weight: number; + total_score: number; + is_public: boolean; + favorited_by: Array<{ + id: string; + }>; + userFavoritePacks?: Array<{ userId: string }>; + favorites_count: number; + owner_id: string | { id: string }; + destination: string; + createdAt: string; + owners: Array<{ any: any }>; + duration: string; + type: FeedType; + itemPacks?: any[]; +} + +export interface FeedCardProps
{ + id: string; + title: string; + cardType: CardType; + createdAt: string; + details: Details; + ownerId: string; + favoriteCount: number; + isUserFavorite: boolean; + toggleFavorite: () => void; +} diff --git a/packages/app/modules/feed/screens/FeedScreen.tsx b/packages/app/modules/feed/screens/FeedScreen.tsx index db33bb99b..70c766d2e 100644 --- a/packages/app/modules/feed/screens/FeedScreen.tsx +++ b/packages/app/modules/feed/screens/FeedScreen.tsx @@ -149,13 +149,16 @@ const Feed = ({ feedType = 'public' }: FeedProps) => { ( + + )} keyExtractor={(item) => item?.id + item?.type} renderItem={({ item }) => ( )} ListFooterComponent={() => } diff --git a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx index 33bf44dd2..02723e320 100644 --- a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx +++ b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx @@ -1,8 +1,9 @@ import React from 'react'; import Carousel from 'app/components/carousel'; import { useFeed } from '../../hooks'; -import { default as FeedPreviewCard, type FeedItem } from './FeedPreviewCard'; import Loader from 'app/components/Loader'; +import { FeedCard, type FeedItem } from 'app/modules/feed'; +import { View } from 'tamagui'; interface FeedPreviewScrollProps { itemWidth: number; @@ -24,9 +25,11 @@ const FeedPreviewScroll: React.FC = ({ {feedData ?.filter((item): item is FeedItem => item.type !== null) .map((item: FeedItem) => { - const linkStr = `/${item.type}/${item.id}`; + const linkStr = `/pack/${item.id}`; return linkStr ? ( - + + + ) : null; })} diff --git a/packages/app/modules/feed/widgets/FeedPreview/FeedPreviewCard.tsx b/packages/app/modules/feed/widgets/FeedPreview/FeedPreviewCard.tsx deleted file mode 100644 index 0453036d8..000000000 --- a/packages/app/modules/feed/widgets/FeedPreview/FeedPreviewCard.tsx +++ /dev/null @@ -1,240 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/react-in-jsx-scope */ -import { useState } from 'react'; -import { RText as OriginalRText, RStack } from '@packrat/ui'; -import { RLink } from '@packrat/ui'; -import { type LayoutChangeEvent, View } from 'react-native'; -import useCustomStyles from 'app/hooks/useCustomStyles'; -import loadStyles from './feedpreview.style'; -import { AntDesign, Fontisto, MaterialIcons } from '@expo/vector-icons'; -import useTheme from 'app/hooks/useTheme'; -import { useItemWeightUnit } from 'app/modules/item'; -import { convertWeight } from 'app/utils/convertWeight'; -import { formatNumber } from 'app/utils/formatNumber'; -import { hexToRGBA } from 'app/utils/colorFunctions'; - -// TODO FeedItem is one of: trip, pack, similar pack & item -export type FeedItem = any; - -interface FeedPreviewCardProps { - linkStr: string; - item: FeedItem; - feedType: string; -} - -const RText: any = OriginalRText; - -const FeedPreviewCard: React.FC = ({ - linkStr, - item, - feedType, -}) => { - const { currentTheme } = useTheme(); - const styles = useCustomStyles(loadStyles); - const [weightUnit] = useItemWeightUnit(); - const formattedWeight = convertWeight( - item.total_weight ?? item.weight, - item.unit ?? 'g', - weightUnit, - ); - const [cardWidth, setCardWidth] = useState(); - - const handleSetCardWidth = (event: LayoutChangeEvent) => { - const { width } = event.nativeEvent.layout; - setCardWidth(width); - }; - - if (feedType == 'similarItems') { - return ( - - - - - - - - - {item.name} - - - - {formatNumber(formattedWeight)} - {weightUnit} - - - - Qty: {item.quantity} - - - - - {new Date(item.createdAt).toLocaleString('en-US', { - month: 'short', - day: '2-digit', - ...(new Date(item.createdAt).getFullYear() == - new Date().getFullYear() - ? {} - : { year: 'numeric' }), - })} - - - - - - ); - } - - return ( - - - - - - - - - - {item.name} - - - - {formatNumber(formattedWeight)} - {weightUnit} - - - - - {item.favorites_count} - - - - - - {new Date(item.createdAt).toLocaleString('en-US', { - month: 'short', - day: '2-digit', - ...(new Date(item.createdAt).getFullYear() == - new Date().getFullYear() - ? {} - : { year: 'numeric' }), - })} - - - - Ttl Score: {item.total_score} - - - - - - ); -}; - -export default FeedPreviewCard; diff --git a/packages/app/modules/pack/components/PackCard/PackCard.tsx b/packages/app/modules/pack/components/PackCard/PackCard.tsx new file mode 100644 index 000000000..8c7f42f69 --- /dev/null +++ b/packages/app/modules/pack/components/PackCard/PackCard.tsx @@ -0,0 +1,18 @@ +import React, { type FC } from 'react'; +import { type FeedCardProps } from 'app/modules/feed'; +import { type PackDetails } from 'app/modules/pack/model'; +import { PackPrimaryCard } from './PackPrimaryCard'; +import { PackSecondaryCard } from './PackSecondaryCard'; + +interface PackCardProps extends FeedCardProps {} + +const PackCards = { + primary: PackPrimaryCard, + secondary: PackSecondaryCard, +}; + +export const PackCard: FC = (props) => { + const PackCardComponent = PackCards[props.cardType]; + + return ; +}; diff --git a/packages/app/modules/pack/components/PackCard/PackImage.tsx b/packages/app/modules/pack/components/PackCard/PackImage.tsx new file mode 100644 index 000000000..868841090 --- /dev/null +++ b/packages/app/modules/pack/components/PackCard/PackImage.tsx @@ -0,0 +1,29 @@ +import React, { type FC } from 'react'; +import { View } from '@packrat/ui'; +import { Package } from '@tamagui/lucide-icons'; +import { type ViewProps } from 'tamagui'; + +interface PackImageProps { + style?: ViewProps['style']; +} + +export const PackImage: FC = ({ style = {} }) => { + return ( + + + + + + ); +}; diff --git a/packages/app/modules/pack/components/PackCard/PackPrimaryCard.tsx b/packages/app/modules/pack/components/PackCard/PackPrimaryCard.tsx new file mode 100644 index 000000000..4c624cded --- /dev/null +++ b/packages/app/modules/pack/components/PackCard/PackPrimaryCard.tsx @@ -0,0 +1,59 @@ +import { Card, Details, RLink, RStack, RText } from '@packrat/ui'; +import React, { type FC } from 'react'; +import { PackImage } from './PackImage'; +import { + FavoriteButton, + CreatedAtLabel, + type FeedCardProps, +} from 'app/modules/feed'; +import { type PackDetails } from 'app/modules/pack/model'; +import { DuplicateIcon } from 'app/assets/icons'; +import { useItemWeightUnit } from 'app/modules/item'; +import { convertWeight } from 'app/utils/convertWeight'; + +interface PackCardProps extends FeedCardProps {} + +export const PackPrimaryCard: FC = (props) => { + const [weightUnit] = useItemWeightUnit(); + const packDetails = Object.entries(props.details).map(([key, value]) => ({ + key, + label: key, + value: + key === 'weight' + ? `${convertWeight(value, 'g', weightUnit)} ${weightUnit}` + : value, + })); + + return ( + } + subtitle={} + actions={ + + { + e.stopPropagation(); + e.preventDefault(); + props.toggleFavorite(); + }} + /> + + + + + View owner + + + } + content={
} + type={props.cardType} + /> + ); +}; diff --git a/packages/app/modules/pack/components/PackCard/PackSecondaryCard.tsx b/packages/app/modules/pack/components/PackCard/PackSecondaryCard.tsx new file mode 100644 index 000000000..1f222a45f --- /dev/null +++ b/packages/app/modules/pack/components/PackCard/PackSecondaryCard.tsx @@ -0,0 +1,38 @@ +import { Card, RStack } from '@packrat/ui'; +import React, { type FC } from 'react'; +import { PackImage } from './PackImage'; +import { + CreatedAtLabel, + FavoriteButton, + type FeedCardProps, +} from 'app/modules/feed'; +import { type PackDetails } from 'app/modules/pack/model'; + +interface PackCardProps extends FeedCardProps {} + +export const PackSecondaryCard: FC = (props) => { + return ( + + } + subtitle={} + actions={ + + { + e.stopPropagation(); + e.preventDefault(); + props.toggleFavorite(); + }} + /> + + } + type={props.cardType} + /> + ); +}; diff --git a/packages/app/modules/pack/components/PackCard/index.ts b/packages/app/modules/pack/components/PackCard/index.ts new file mode 100644 index 000000000..184fc6a3b --- /dev/null +++ b/packages/app/modules/pack/components/PackCard/index.ts @@ -0,0 +1 @@ +export { PackCard } from './PackCard'; diff --git a/packages/app/modules/pack/components/index.ts b/packages/app/modules/pack/components/index.ts index 3c3c80097..3fa8751e1 100644 --- a/packages/app/modules/pack/components/index.ts +++ b/packages/app/modules/pack/components/index.ts @@ -5,3 +5,4 @@ export { EditPackItemModal, } from './PackTable'; export { CopyPackModal } from './CopyPackModal'; +export { PackCard } from './PackCard'; diff --git a/packages/app/modules/pack/index.ts b/packages/app/modules/pack/index.ts index de11d3544..d60ba23cc 100644 --- a/packages/app/modules/pack/index.ts +++ b/packages/app/modules/pack/index.ts @@ -1,3 +1,4 @@ export * from './hooks'; export * from './components'; export { AddPackScreen, PackDetailsScreen } from './screens'; +export { type PackDetails } from './model'; diff --git a/packages/app/modules/pack/model.ts b/packages/app/modules/pack/model.ts new file mode 100644 index 000000000..960e743ab --- /dev/null +++ b/packages/app/modules/pack/model.ts @@ -0,0 +1,5 @@ +export interface PackDetails { + score?: number; + quantity: number; + weight: number; +} diff --git a/packages/app/modules/user/components/UserDataCard.tsx b/packages/app/modules/user/components/UserDataCard.tsx index fdbe319ac..e28684ba4 100644 --- a/packages/app/modules/user/components/UserDataCard.tsx +++ b/packages/app/modules/user/components/UserDataCard.tsx @@ -14,7 +14,7 @@ import { truncateString } from 'app/utils/truncateString'; import { useEditPack } from 'app/modules/pack'; import { Platform } from 'react-native'; import { useEditTrips } from 'app/hooks/trips'; -import { useAddFavorite } from 'app/hooks/favorites'; +import { useAddFavorite } from 'app/modules/feed'; import useTheme from 'app/hooks/useTheme'; const RText: any = OriginalRText; diff --git a/packages/app/modules/user/components/UserDetailList.tsx b/packages/app/modules/user/components/UserDetailList.tsx index 5fa4aa831..2b2a92386 100644 --- a/packages/app/modules/user/components/UserDetailList.tsx +++ b/packages/app/modules/user/components/UserDetailList.tsx @@ -51,7 +51,9 @@ export const UserDataList = ({ data }: DataListProps) => { ]} footerComponent={undefined} > - + { data={filteredData.slice(0, 2)} horizontal={false} keyExtractor={(item) => item?.id} + ItemSeparatorComponent={() => } renderItem={({ item }) => ( - + )} showsVerticalScrollIndicator={false} maxToRenderPerBatch={2} @@ -93,8 +101,14 @@ export const UserDataList = ({ data }: DataListProps) => { data={filteredData} horizontal={false} keyExtractor={(item) => item?._id} + ItemSeparatorComponent={() => } renderItem={({ item }) => ( - + )} showsVerticalScrollIndicator={false} maxToRenderPerBatch={2} diff --git a/packages/app/modules/user/hooks/useProfile.ts b/packages/app/modules/user/hooks/useProfile.ts index 938b2a0e4..88057499e 100644 --- a/packages/app/modules/user/hooks/useProfile.ts +++ b/packages/app/modules/user/hooks/useProfile.ts @@ -1,4 +1,4 @@ -import { useFetchUserFavorites } from 'app/hooks/favorites'; +import { useFetchUserFavorites } from 'app/modules/feed'; import { useUserPacks } from 'app/modules/pack'; import { useUserTrips } from 'app/hooks/singletrips'; import { useAuthUser, useMatchesCurrentUser } from 'app/modules/auth'; diff --git a/packages/ui/src/Details/Details.tsx b/packages/ui/src/Details/Details.tsx new file mode 100644 index 000000000..34b772ce8 --- /dev/null +++ b/packages/ui/src/Details/Details.tsx @@ -0,0 +1,49 @@ +import React, { type FC, type ReactNode } from 'react'; +import { ListItem, type ListItemProps, YGroup, Text, useTheme } from 'tamagui'; + +interface DetailsItem extends ListItemProps { + key: string | number; + label: ReactNode; + value: ReactNode; +} +interface DetailsProps { + items: DetailsItem[]; +} +export const Details: FC = ({ items }) => { + const theme = useTheme(); + const primaryColor = theme.primary; + + return ( + + {items.map(({ key, label, value, ...item }) => ( + + + + {label}: + + {value} + + + ))} + + ); +}; diff --git a/packages/ui/src/Details/index.ts b/packages/ui/src/Details/index.ts new file mode 100644 index 000000000..b358dd569 --- /dev/null +++ b/packages/ui/src/Details/index.ts @@ -0,0 +1 @@ +export { Details } from './Details'; diff --git a/packages/ui/src/card/Card.tsx b/packages/ui/src/card/Card.tsx new file mode 100644 index 000000000..d06775412 --- /dev/null +++ b/packages/ui/src/card/Card.tsx @@ -0,0 +1,23 @@ +import React, { type FC } from 'react'; +import { type CardType, type BaseCardProps } from './model'; +import { PrimaryCard } from './PrimaryCard'; +import { SecondaryCard } from './SecondaryCard'; +import { RLink } from '..'; + +const CardComponents = { + primary: PrimaryCard, + secondary: SecondaryCard, +}; + +interface CardProps extends BaseCardProps { + type: CardType; +} +export const Card: FC = ({ type, ...props }) => { + const CardComponent = CardComponents[type]; + + return ( + + + + ); +}; diff --git a/packages/ui/src/card/PrimaryCard.tsx b/packages/ui/src/card/PrimaryCard.tsx new file mode 100644 index 000000000..d10a799fb --- /dev/null +++ b/packages/ui/src/card/PrimaryCard.tsx @@ -0,0 +1,53 @@ +import React, { type FC } from 'react'; +import { type BaseCardProps } from './model'; +import { Card, useTheme, View, XStack, YStack } from 'tamagui'; +import { RText } from '@packrat/ui'; + +interface PrimaryCardProps extends BaseCardProps {} + +export const PrimaryCard: FC = (props) => { + const theme = useTheme(); + return ( + + + + + {props.image} + + + + {props.title} + + + {props.subtitle} + + {props.actions} + + + + + {props.content} + + + {props.footer} + + + ); +}; diff --git a/packages/ui/src/card/SecondaryCard.tsx b/packages/ui/src/card/SecondaryCard.tsx new file mode 100644 index 000000000..2459c44ac --- /dev/null +++ b/packages/ui/src/card/SecondaryCard.tsx @@ -0,0 +1,54 @@ +import React, { type FC } from 'react'; +import { type BaseCardProps } from './model'; +import { Card, View, XStack, YStack } from 'tamagui'; +import RText from '../RText'; + +interface SecondaryCardProps extends Omit {} + +export const SecondaryCard: FC = (props) => { + return ( + + {props.image} + + + + + {props.title} + + + {props.subtitle} + + + {props.actions} + + + + ); +}; diff --git a/packages/ui/src/card/index.ts b/packages/ui/src/card/index.ts new file mode 100644 index 000000000..9211d038e --- /dev/null +++ b/packages/ui/src/card/index.ts @@ -0,0 +1,2 @@ +export { Card } from './Card'; +export * from './model'; diff --git a/packages/ui/src/card/model.ts b/packages/ui/src/card/model.ts new file mode 100644 index 000000000..ce048554f --- /dev/null +++ b/packages/ui/src/card/model.ts @@ -0,0 +1,13 @@ +import { type ReactNode } from 'react'; + +export type CardType = 'primary' | 'secondary'; + +export interface BaseCardProps { + link: string; + title: ReactNode; + image: ReactNode; + subtitle: ReactNode; + content?: ReactNode; + footer?: ReactNode; + actions?: ReactNode; +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 3d15e888b..38c8417df 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -78,7 +78,9 @@ export { export * from './InputText'; export * from './form'; - +export * from './card'; +export * from './Details'; +export { View } from 'tamagui'; export { config } from './tamagui.config'; // export * from 'tamagui'; export * from '@tamagui/toast'; From b26f95d8b3db83a0ac2f4afc78e9fdf87203cb49 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 7 Sep 2024 01:53:30 -0400 Subject: [PATCH 015/127] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20add=20native=20toa?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/config/axios.ts | 31 +++++++++---------- packages/app/config/trpcAxiosClient.ts | 16 +++++----- .../app/context/ThirdPartyThemeProviders.tsx | 2 ++ .../password-reset/useRequestEmailModal.ts | 10 +++--- .../pack/components/PackCard/PackImage.tsx | 4 +-- packages/app/package.json | 2 +- packages/app/utils/ToastUtils.ts | 29 ++--------------- yarn.lock | 4 +-- 8 files changed, 35 insertions(+), 63 deletions(-) diff --git a/packages/app/config/axios.ts b/packages/app/config/axios.ts index 340e9becc..efadbd78a 100644 --- a/packages/app/config/axios.ts +++ b/packages/app/config/axios.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { api } from 'app/constants/api'; // import { store } from '../store/store'; -import { InformUser } from 'app/utils/ToastUtils'; +import { toast } from 'app/utils/ToastUtils'; import { dispatchProgress, progressActions } from '../atoms/progressStore'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -70,11 +70,10 @@ const responseInterceptor = (response) => { const responseMessage = response.headers['x-response-message']; if (responseMessage) { - InformUser({ + toast({ title: responseMessage, - placement: 'bottom', - duration: 3000, - style: { backgroundColor: response.status === 200 ? 'green' : 'red' }, + duration: 3, + preset: response.status === 200 ? 'done' : 'error', }); } @@ -86,11 +85,10 @@ const responseInterceptor2 = (response) => { const responseMessage = response.headers['x-response-message']; if (responseMessage) { - InformUser({ + toast({ title: responseMessage, - placement: 'bottom', - duration: 3000, - style: { backgroundColor: response.status === 200 ? 'green' : 'red' }, + duration: 3, + preset: response.status === 200 ? 'done' : 'error', }); } @@ -116,11 +114,11 @@ const responseErrorInterceptor = (error) => { const errorMessage = 'message' in error ? error.message : 'Something went wrong'; - InformUser({ + + toast({ title: errorMessage, - placement: 'bottom', - duration: 3000, - style: { backgroundColor: 'red' }, + duration: 3, + preset: 'error', }); return Promise.reject(error); @@ -133,11 +131,10 @@ const responseErrorInterceptor2 = (error) => { const errorMessage = 'message' in error ? error.message : 'Something went wrong'; - InformUser({ + toast({ title: errorMessage, - placement: 'bottom', - duration: 3000, - style: { backgroundColor: 'red' }, + duration: 3, + preset: 'error', }); setTimeout(() => { diff --git a/packages/app/config/trpcAxiosClient.ts b/packages/app/config/trpcAxiosClient.ts index 6eed78e04..cd690c149 100644 --- a/packages/app/config/trpcAxiosClient.ts +++ b/packages/app/config/trpcAxiosClient.ts @@ -1,5 +1,5 @@ import axios, { AxiosResponse } from 'axios'; -import { InformUser } from 'app/utils/ToastUtils'; +import { toast } from 'app/utils/ToastUtils'; import { logoutAuthUser } from 'app/utils/userUtils'; import { getErrorMessageFromError } from 'app/utils/apiUtils'; @@ -25,11 +25,10 @@ const responseInterceptor = (response: AxiosResponse) => { return response; } - InformUser({ + toast({ title: 'Confirmed! Your submission was successful.', - placement: 'bottom', - duration: 3000, - style: { backgroundColor: 'green' }, + duration: 3, + preset: 'done', }); return response; @@ -52,11 +51,10 @@ const responseErrorInterceptor = (response: AxiosResponse) => { const responseMessage = getErrorMessageFromError(response); if (responseMessage) { - InformUser({ + toast({ title: responseMessage, - placement: 'bottom', - duration: 3000, - style: { backgroundColor: 'red' }, + duration: 3, + preset: 'error', }); } diff --git a/packages/app/context/ThirdPartyThemeProviders.tsx b/packages/app/context/ThirdPartyThemeProviders.tsx index e26f75fb1..337398fe8 100644 --- a/packages/app/context/ThirdPartyThemeProviders.tsx +++ b/packages/app/context/ThirdPartyThemeProviders.tsx @@ -12,6 +12,7 @@ import { } from '../theme'; import FontLoader from './FontLoader'; import { setupDev } from 'tamagui'; +import { Toaster } from 'burnt/web'; const ThirdPartyProviders = ({ children, isDark = false }) => { setupDev({ @@ -34,6 +35,7 @@ const ThirdPartyProviders = ({ children, isDark = false }) => { {children} + diff --git a/packages/app/hooks/password-reset/useRequestEmailModal.ts b/packages/app/hooks/password-reset/useRequestEmailModal.ts index c066663c3..3843019ec 100644 --- a/packages/app/hooks/password-reset/useRequestEmailModal.ts +++ b/packages/app/hooks/password-reset/useRequestEmailModal.ts @@ -1,5 +1,5 @@ import useTheme from 'app/hooks/useTheme'; -import { InformUser } from 'app/utils/ToastUtils'; +import { toast } from 'app/utils/ToastUtils'; import { queryTrpc } from 'app/trpc'; export const useRequestEmailModal = () => { @@ -21,11 +21,11 @@ export const useRequestEmailModal = () => { // TODO - switch to RTK query // await axios.post(`${api}/password-reset`, { email }); closeModal(); - InformUser({ + + toast({ title: 'Password reset email sent', - style: { backgroundColor: currentTheme.colors.text }, - placement: 'bottom', - duration: 5000, + duration: 3, + preset: 'done', }); } catch (error) {} }; diff --git a/packages/app/modules/pack/components/PackCard/PackImage.tsx b/packages/app/modules/pack/components/PackCard/PackImage.tsx index 868841090..18e6cec94 100644 --- a/packages/app/modules/pack/components/PackCard/PackImage.tsx +++ b/packages/app/modules/pack/components/PackCard/PackImage.tsx @@ -1,6 +1,6 @@ import React, { type FC } from 'react'; import { View } from '@packrat/ui'; -import { Package } from '@tamagui/lucide-icons'; +import { Backpack } from '@tamagui/lucide-icons'; import { type ViewProps } from 'tamagui'; interface PackImageProps { @@ -22,7 +22,7 @@ export const PackImage: FC = ({ style = {} }) => { ]} > - + ); diff --git a/packages/app/package.json b/packages/app/package.json index 4e1b34761..ed82bad5b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -70,7 +70,7 @@ "@trpc/server": "^10.45.1", "axios": "^1.4.0", "babel-plugin-transform-inline-environment-variables": "^0.4.4", - "burnt": "^0.12.1", + "burnt": "^0.12.2", "date-fns": "^2.29.3", "dotenv": "^16.0.3", "eslint-plugin-react-native": "^4.0.0", diff --git a/packages/app/utils/ToastUtils.ts b/packages/app/utils/ToastUtils.ts index f72203797..d4a9c859a 100644 --- a/packages/app/utils/ToastUtils.ts +++ b/packages/app/utils/ToastUtils.ts @@ -1,28 +1,3 @@ -import { Toast } from 'native-base'; +import { toast } from 'burnt'; -/** - * Show a toast message to inform the user. - * - * @function InformUser - * @param {Object} options - The options object for the toast message. - * @param {string} options.title - The title of the toast message. - * @param {string} [options.placement="top"] - The placement of the toast message. Possible values are "top", "bottom", "left", or "right". - * @param {number} [options.duration=2000] - The duration in milliseconds for which the toast message should be displayed. - * @param {Object} [options.style={backgroundColor: 'green'}] - The style object for the toast message. - * @returns {void} - * - * @example - * // Show a toast message with default options - * InformUser({ title: "Hello, world!" }); - * - * // Show a toast message with custom options - * InformUser({ title: "Custom Toast", placement: "bottom", duration: 3000 }); - */ -export const InformUser = ({ title, placement, duration, style }) => { - Toast.show({ - title, - placement, - duration, - style, - }); -}; +export { toast }; diff --git a/yarn.lock b/yarn.lock index 9ccf90679..88f32c3fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13641,7 +13641,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^6.21.0" axios: "npm:^1.4.0" babel-plugin-transform-inline-environment-variables: "npm:^0.4.4" - burnt: "npm:^0.12.1" + burnt: "npm:^0.12.2" date-fns: "npm:^2.29.3" dotenv: "npm:^16.0.3" eslint: "npm:^8.56.0" @@ -15640,7 +15640,7 @@ __metadata: languageName: unknown linkType: soft -"burnt@npm:^0.12.1": +"burnt@npm:^0.12.1, burnt@npm:^0.12.2": version: 0.12.2 resolution: "burnt@npm:0.12.2" dependencies: From e54550c507db3b91c19066389cc0750ed525a5c1 Mon Sep 17 00:00:00 2001 From: Taron Date: Sat, 7 Sep 2024 12:19:58 +0400 Subject: [PATCH 016/127] fix: fix profile screen crop --- packages/app/modules/user/screens/SettingsScreen.tsx | 8 ++++++-- packages/app/modules/user/widgets/ProfileContainer.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/app/modules/user/screens/SettingsScreen.tsx b/packages/app/modules/user/screens/SettingsScreen.tsx index 719ef9e58..82c096ca3 100644 --- a/packages/app/modules/user/screens/SettingsScreen.tsx +++ b/packages/app/modules/user/screens/SettingsScreen.tsx @@ -50,8 +50,12 @@ export function SettingsScreen() { gap={8} width="fit-content" maw="100%" - paddingVertical={20} - paddingHorizontal={8} + style={{ + paddingTop: 20, + paddingBottom: 100, + paddingLeft: 20, + paddingRight: 20, + }} marginHorizontal="auto" marginVertical={40} > diff --git a/packages/app/modules/user/widgets/ProfileContainer.tsx b/packages/app/modules/user/widgets/ProfileContainer.tsx index 725ae076e..58201ceb0 100644 --- a/packages/app/modules/user/widgets/ProfileContainer.tsx +++ b/packages/app/modules/user/widgets/ProfileContainer.tsx @@ -210,7 +210,7 @@ export function ProfileContainer({ id = null }) { styles.mainContainer, Platform.OS == 'web' ? { minHeight: '100vh' } - : { minHeight: '100%', paddingBottom: 40 }, + : { minHeight: '100%', paddingBottom: 100 }, ]} >
Date: Sat, 7 Sep 2024 11:48:38 -0400 Subject: [PATCH 017/127] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20replace=20native?= =?UTF-8?q?=20base=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expo/app/(app)/(drawer)/(tabs)/search.tsx | 14 +++++----- packages/app/components/chat/index.tsx | 1 - packages/app/components/progress/index.tsx | 27 ++++++++++++------- .../app/context/ThirdPartyThemeProviders.tsx | 25 +++++++---------- .../item/components/ItemRow/ItemRow.tsx | 7 +++-- .../pack/screens/PackDetailsScreen.tsx | 9 +++++-- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/apps/expo/app/(app)/(drawer)/(tabs)/search.tsx b/apps/expo/app/(app)/(drawer)/(tabs)/search.tsx index e63bf7dd0..47dc6ba82 100644 --- a/apps/expo/app/(app)/(drawer)/(tabs)/search.tsx +++ b/apps/expo/app/(app)/(drawer)/(tabs)/search.tsx @@ -1,13 +1,11 @@ -import React from 'react'; -import { Platform, type TextInput } from 'react-native'; -import { useCallback, useRef } from 'react'; -import { Stack, useFocusEffect } from 'expo-router'; -import Head from 'expo-router/head'; -import { useRouter } from 'app/hooks/router'; +import { RStack, View } from '@packrat/ui'; import { PlacesAutocomplete } from 'app/components/PlacesAutocomplete'; -import { RStack } from '@packrat/ui'; +import { useRouter } from 'app/hooks/router'; import useTheme from 'app/hooks/useTheme'; -import { View } from 'native-base'; +import { Stack, useFocusEffect } from 'expo-router'; +import Head from 'expo-router/head'; +import React, { useCallback, useRef } from 'react'; +import { Platform, type TextInput } from 'react-native'; interface SearchResult { properties: { diff --git a/packages/app/components/chat/index.tsx b/packages/app/components/chat/index.tsx index fe18c7e51..cccdf031e 100644 --- a/packages/app/components/chat/index.tsx +++ b/packages/app/components/chat/index.tsx @@ -21,7 +21,6 @@ import { SuggestionList, } from '../../components/Suggestion'; import useTheme from 'app/hooks/useTheme'; -import colors from 'native-base/lib/typescript/theme/base/colors'; interface ChatComponentProps { showChatSelector?: boolean; diff --git a/packages/app/components/progress/index.tsx b/packages/app/components/progress/index.tsx index e4990eb08..4e57e1072 100644 --- a/packages/app/components/progress/index.tsx +++ b/packages/app/components/progress/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; -import { Progress } from 'native-base'; import useTheme from 'app/hooks/useTheme'; import { useProgressStore } from 'app/atoms/progressStore'; +import { View } from '@packrat/ui'; const ProgressBarComponent = () => { const { currentTheme } = useTheme(); @@ -35,14 +35,23 @@ const ProgressBarComponent = () => { }, [localCurrentValue, targetValue, operationId]); return ( - + + + ); }; diff --git a/packages/app/context/ThirdPartyThemeProviders.tsx b/packages/app/context/ThirdPartyThemeProviders.tsx index 337398fe8..3e0eb739d 100644 --- a/packages/app/context/ThirdPartyThemeProviders.tsx +++ b/packages/app/context/ThirdPartyThemeProviders.tsx @@ -3,7 +3,6 @@ import { TamaguiProvider, Theme as TamaguiTheme } from 'tamagui'; import { ToastProvider } from '@tamagui/toast'; import config from '../theme/tamagui.config'; import { ThemeProvider as RNPaperThemeProvider } from 'react-native-paper'; -import { NativeBaseProvider } from 'native-base'; import { darkPaperTheme, lightThemePaper, @@ -27,20 +26,16 @@ const ThirdPartyProviders = ({ children, isDark = false }) => { return ( - - - - - - {children} - - - - - - + + + + + {children} + + + + + ); }; diff --git a/packages/app/modules/item/components/ItemRow/ItemRow.tsx b/packages/app/modules/item/components/ItemRow/ItemRow.tsx index 53e50afbd..6f1b60e6b 100644 --- a/packages/app/modules/item/components/ItemRow/ItemRow.tsx +++ b/packages/app/modules/item/components/ItemRow/ItemRow.tsx @@ -1,9 +1,8 @@ -import { Container } from 'native-base'; import { RText, RStack } from '@packrat/ui'; import Checkbox from 'expo-checkbox'; import { FontAwesome } from '@expo/vector-icons'; import useCustomStyles from 'app/hooks/useCustomStyles'; -import { useItemRow } from 'app/hooks/itemrow'; +import { useItemRow } from 'app/modules/item'; interface ItemRowProps { packName: string; @@ -14,7 +13,7 @@ export const ItemRow: React.FC = ({ packName }) => { const styles = useCustomStyles(loadStyles); return ( - + = ({ packName }) => { - + ); }; diff --git a/packages/app/modules/pack/screens/PackDetailsScreen.tsx b/packages/app/modules/pack/screens/PackDetailsScreen.tsx index a64752568..e0e5def68 100644 --- a/packages/app/modules/pack/screens/PackDetailsScreen.tsx +++ b/packages/app/modules/pack/screens/PackDetailsScreen.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { CLIENT_URL } from '@packrat/config'; -import { RH3, RText } from '@packrat/ui'; +import { RH3, RSpinner, RText } from '@packrat/ui'; import { useAuthUser } from 'app/modules/auth'; import Layout from 'app/components/layout/Layout'; import { @@ -57,7 +57,12 @@ export function PackDetailsScreen() { const isError = error !== null; - if (isLoading) return Loading...; + if (isLoading) + return ( + + + + ); return ( From c20839ca9e375a6b4392ee8d7272d70d6f112be9 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 7 Sep 2024 12:01:41 -0400 Subject: [PATCH 018/127] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20improve=20feed=20p?= =?UTF-8?q?review=20ui=20on=20native?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/components/carousel/index.tsx | 10 ++++++---- packages/app/components/layout/Layout.tsx | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/app/components/carousel/index.tsx b/packages/app/components/carousel/index.tsx index 49ede65bd..099405f72 100644 --- a/packages/app/components/carousel/index.tsx +++ b/packages/app/components/carousel/index.tsx @@ -15,7 +15,7 @@ interface CarouselProps { iconColor?: string; } -const { height, width } = Dimensions.get('window'); +const { width: screenWidth } = Dimensions.get('window'); const Carousel: React.FC = ({ children = [], @@ -37,7 +37,7 @@ const Carousel: React.FC = ({ const scrollToIndex = (index: number) => { if (index >= 0 && index < children.length && scrollViewRef.current) { scrollViewRef.current.scrollTo({ - x: index * 220, + x: index * (itemWidth || 220), // Use itemWidth if passed y: 0, animated: true, }); @@ -49,13 +49,14 @@ const Carousel: React.FC = ({ + {/* Show buttons only on web */} {Platform.OS === 'web' && ( = ({ ref={scrollViewRef} horizontal scrollEnabled - // gestureEnabled={false} // Add this prop style={styles.carousel} showsHorizontalScrollIndicator={false} contentContainerStyle={{ flexDirection: 'row' }} @@ -82,6 +82,7 @@ const Carousel: React.FC = ({ = ({ ))} + {/* Show buttons only on web */} {Platform.OS === 'web' && ( Date: Sat, 7 Sep 2024 21:03:02 +0400 Subject: [PATCH 019/127] add trips init --- .../app/hooks/navigation/useNavigationList.ts | 15 ++-- .../feed/components/FeedCard/FeedCard.tsx | 9 ++- .../modules/feed/components/FeedCard/utils.ts | 33 ++++++++ .../feed/components/FeedSearchFilter.tsx | 78 +++++++------------ packages/app/modules/feed/hooks/useFeed.ts | 3 +- packages/app/modules/feed/model.ts | 40 +++++++--- .../app/modules/feed/screens/FeedScreen.tsx | 17 +--- .../LocationLabel/LocationLabel.tsx | 18 +++++ .../trip/components/LocationLabel/index.ts | 1 + .../trip/components/TripCard/TripCard.tsx | 18 +++++ .../trip/components/TripCard/TripImage.tsx | 29 +++++++ .../components/TripCard/TripPrimaryCard.tsx | 61 +++++++++++++++ .../components/TripCard/TripSecondaryCard.tsx | 38 +++++++++ .../modules/trip/components/TripCard/index.ts | 1 + packages/app/modules/trip/components/index.ts | 1 + packages/app/modules/trip/index.ts | 1 + packages/app/modules/trip/model.ts | 6 ++ packages/app/screens/trip/createTrip.tsx | 3 +- 18 files changed, 283 insertions(+), 89 deletions(-) create mode 100644 packages/app/modules/trip/components/LocationLabel/LocationLabel.tsx create mode 100644 packages/app/modules/trip/components/LocationLabel/index.ts create mode 100644 packages/app/modules/trip/components/TripCard/TripCard.tsx create mode 100644 packages/app/modules/trip/components/TripCard/TripImage.tsx create mode 100644 packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx create mode 100644 packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx create mode 100644 packages/app/modules/trip/components/TripCard/index.ts create mode 100644 packages/app/modules/trip/components/index.ts create mode 100644 packages/app/modules/trip/model.ts diff --git a/packages/app/hooks/navigation/useNavigationList.ts b/packages/app/hooks/navigation/useNavigationList.ts index 3962bccdc..f14749eba 100644 --- a/packages/app/hooks/navigation/useNavigationList.ts +++ b/packages/app/hooks/navigation/useNavigationList.ts @@ -85,14 +85,13 @@ const logedInMenuItems: NavigationItem[] = [ text: 'Feed', iconSource: MaterialCommunityIcons, }, - // DISABLE TROP TEMP - // { - // type: NavigationItemTypeEnum.LINK, - // href: '/trips', - // icon: 'routes', - // text: 'Trips', - // iconSource: MaterialCommunityIcons, - // }, + { + type: NavigationItemTypeEnum.LINK, + href: '/trips', + icon: 'routes', + text: 'Trips', + iconSource: MaterialCommunityIcons, + }, { type: NavigationItemTypeEnum.LINK, href: '/packs', diff --git a/packages/app/modules/feed/components/FeedCard/FeedCard.tsx b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx index 5374ff139..58098b2ac 100644 --- a/packages/app/modules/feed/components/FeedCard/FeedCard.tsx +++ b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx @@ -1,21 +1,24 @@ import React, { type FC } from 'react'; -import { type FeedItem, type FeedType } from 'app/modules/feed/model'; -import { feedItemPackCardConverter } from './utils'; +import { type FeedItem, type FeedResource } from 'app/modules/feed/model'; +import { feedItemPackCardConverter, feedItemTripCardConverter } from './utils'; import { PackCard } from 'app/modules/pack'; import { type CardType } from '@packrat/ui'; import { useAddFavorite } from 'app/modules/feed'; import { useAuthUser } from 'app/modules/auth'; +import { TripCard } from 'app/modules/trip'; const convertersByType = { pack: feedItemPackCardConverter, + trip: feedItemTripCardConverter, }; const cardComponentsByType = { pack: PackCard, + trip: TripCard, }; interface FeedCardProps { - feedType: FeedType; + feedType: FeedResource; cardType: CardType; item: FeedItem; } diff --git a/packages/app/modules/feed/components/FeedCard/utils.ts b/packages/app/modules/feed/components/FeedCard/utils.ts index ea18d5b94..1162372cb 100644 --- a/packages/app/modules/feed/components/FeedCard/utils.ts +++ b/packages/app/modules/feed/components/FeedCard/utils.ts @@ -2,6 +2,7 @@ import { type FeedCardProps, type FeedItem } from 'modules/feed/model'; import { formatDistanceToNowStrict } from 'date-fns'; import { type PackDetails } from 'app/modules/pack'; import { truncateString } from 'app/utils/truncateString'; +import { type TripDetails } from 'modules/trip/model'; type Converter = ( input: Input, @@ -12,6 +13,10 @@ export const feedItemPackCardConverter: Converter< FeedItem, Omit, 'cardType' | 'toggleFavorite'> > = (input, currentUserId) => { + if (input.type !== 'pack') { + console.error('Expected input to be of type pack'); + return null; + } return { id: input.id, createdAt: formatDistanceToNowStrict(new Date(input.createdAt), { @@ -38,3 +43,31 @@ export const feedItemPackCardConverter: Converter< favoriteCount: input.favorites_count, }; }; + +export const feedItemTripCardConverter: Converter< + FeedItem, + Omit, 'cardType' | 'toggleFavorite'> +> = (input, currentUserId) => { + if (input.type !== 'trip') { + console.error('Expected input to be of type trip'); + return null; + } + return { + id: input.id, + createdAt: formatDistanceToNowStrict(new Date(input.createdAt), { + addSuffix: false, + }), + title: truncateString(input.name, 25), + ownerId: + typeof input.owner_id === 'string' + ? input.owner_id + : input.owner_id?.id || '', + details: { + destination: input.destination, + description: truncateString(input.description, 100), + startDate: input.start_date, + endDate: input.end_date, + }, + favoriteCount: input.favorites_count, + }; +}; diff --git a/packages/app/modules/feed/components/FeedSearchFilter.tsx b/packages/app/modules/feed/components/FeedSearchFilter.tsx index 555d337c0..16504b13a 100644 --- a/packages/app/modules/feed/components/FeedSearchFilter.tsx +++ b/packages/app/modules/feed/components/FeedSearchFilter.tsx @@ -10,8 +10,10 @@ import { Form, InputWithIcon, DropdownComponent, + RSwitch, } from '@packrat/ui'; import { Search, X } from '@tamagui/lucide-icons'; +import { Switch } from 'tamagui'; const RStack: any = OriginalRStack; const RText: any = OriginalRText; const RSeparator: any = OriginalRSeparator; @@ -87,24 +89,6 @@ export const FeedSearchFilter = ({ placeholder={`Search ${feedType || 'Feed'}`} value={searchValue} /> - {/* setSearchValue(event.nativeEvent.text)} - /> - onSearch(searchValue)} - icon={ - - } - /> */} @@ -123,45 +107,37 @@ export const FeedSearchFilter = ({ - {/* DISABLE TRIP TEMP */} - Discover Other Users' Public Packs + Packs - {/* - Packs - - - - - - Trips - - - - */} + + + + + Trips + + + + )} = {}) => { diff --git a/packages/app/modules/feed/model.ts b/packages/app/modules/feed/model.ts index cc30e1659..103f7b9d9 100644 --- a/packages/app/modules/feed/model.ts +++ b/packages/app/modules/feed/model.ts @@ -1,31 +1,49 @@ import { type CardType } from '@packrat/ui'; -export type FeedType = 'pack'; +export type FeedType = + | 'public' + | 'userPacks' + | 'userTrips' + | 'similarPacks' + | 'similarItems'; +export type FeedResource = 'pack' | 'trip'; -export interface FeedItem { +export interface BaseFeedItem { id: string; + type: FeedResource; + name: string; owner: { id: string; username: string; }; - name: string; - total_weight: number; - total_score: number; is_public: boolean; favorited_by: Array<{ id: string; }>; - userFavoritePacks?: Array<{ userId: string }>; favorites_count: number; owner_id: string | { id: string }; - destination: string; createdAt: string; owners: Array<{ any: any }>; - duration: string; - type: FeedType; +} + +interface PackFeedItem extends BaseFeedItem { + type: 'pack'; + total_weight: number; + total_score: number; + userFavoritePacks?: Array<{ userId: string }>; itemPacks?: any[]; } +export interface TripFeedItem extends BaseFeedItem { + type: 'trip'; + description: string; + destination: string; + duration: string; + start_date: string; + end_date: string; +} + +export type FeedItem = PackFeedItem | TripFeedItem; export interface FeedCardProps
{ id: string; title: string; @@ -34,6 +52,6 @@ export interface FeedCardProps
{ details: Details; ownerId: string; favoriteCount: number; - isUserFavorite: boolean; - toggleFavorite: () => void; + isUserFavorite?: boolean; + toggleFavorite?: () => void; } diff --git a/packages/app/modules/feed/screens/FeedScreen.tsx b/packages/app/modules/feed/screens/FeedScreen.tsx index 70c766d2e..53dd5438b 100644 --- a/packages/app/modules/feed/screens/FeedScreen.tsx +++ b/packages/app/modules/feed/screens/FeedScreen.tsx @@ -8,7 +8,7 @@ import { useFeed } from 'app/modules/feed'; import { RefreshControl } from 'react-native'; import { RText } from '@packrat/ui'; import { useAuthUser } from 'app/modules/auth'; -import { disableScreen } from 'app/hoc/disableScreen'; +import { type FeedType } from '../model'; const URL_PATHS = { userPacks: '/pack/', @@ -23,18 +23,8 @@ const ERROR_MESSAGES = { userTrips: 'No User Trips Available', }; -interface FeedItem { - id: string; - type: string; -} - -interface SelectedTypes { - pack: boolean; - trip: boolean; -} - interface FeedProps { - feedType?: string; + feedType?: FeedType; } interface UseFeedResult { @@ -67,6 +57,7 @@ const Feed = ({ feedType = 'public' }: FeedProps) => { feedType, selectedTypes, }) as UseFeedResult; + console.log({ data }); const onRefresh = () => { setRefreshing(true); @@ -214,4 +205,4 @@ const loadStyles = (theme) => { }; }; -export default disableScreen(Feed, (props) => props.feedType === 'userTrips'); +export default Feed; diff --git a/packages/app/modules/trip/components/LocationLabel/LocationLabel.tsx b/packages/app/modules/trip/components/LocationLabel/LocationLabel.tsx new file mode 100644 index 000000000..b4146aa82 --- /dev/null +++ b/packages/app/modules/trip/components/LocationLabel/LocationLabel.tsx @@ -0,0 +1,18 @@ +import { XStack, RText } from '@packrat/ui'; +import { MapPin } from '@tamagui/lucide-icons'; +import React, { type FC } from 'react'; + +interface CreatedAtLabelProps { + location: string; +} +export const LocationLabel: FC = ({ location }) => { + return ( + + + {location} + + ); +}; diff --git a/packages/app/modules/trip/components/LocationLabel/index.ts b/packages/app/modules/trip/components/LocationLabel/index.ts new file mode 100644 index 000000000..5543582f5 --- /dev/null +++ b/packages/app/modules/trip/components/LocationLabel/index.ts @@ -0,0 +1 @@ +export { LocationLabel } from './LocationLabel'; diff --git a/packages/app/modules/trip/components/TripCard/TripCard.tsx b/packages/app/modules/trip/components/TripCard/TripCard.tsx new file mode 100644 index 000000000..4db13a10f --- /dev/null +++ b/packages/app/modules/trip/components/TripCard/TripCard.tsx @@ -0,0 +1,18 @@ +import React, { type FC } from 'react'; +import { type FeedCardProps } from 'app/modules/feed'; +import { TripPrimaryCard } from './TripPrimaryCard'; +import { TripSecondaryCard } from './TripSecondaryCard'; +import { type TripDetails } from 'modules/trip/model'; + +interface TripCardProps extends FeedCardProps {} + +const TripCards = { + primary: TripPrimaryCard, + secondary: TripSecondaryCard, +}; + +export const TripCard: FC = (props) => { + const TripCardComponent = TripCards[props.cardType]; + + return ; +}; diff --git a/packages/app/modules/trip/components/TripCard/TripImage.tsx b/packages/app/modules/trip/components/TripCard/TripImage.tsx new file mode 100644 index 000000000..c6e91c122 --- /dev/null +++ b/packages/app/modules/trip/components/TripCard/TripImage.tsx @@ -0,0 +1,29 @@ +import React, { type FC } from 'react'; +import { View } from '@packrat/ui'; +import { Package } from '@tamagui/lucide-icons'; +import { type ViewProps } from 'tamagui'; + +interface TripImageProps { + style?: ViewProps['style']; +} + +export const TripImage: FC = ({ style = {} }) => { + return ( + + + + + + ); +}; diff --git a/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx b/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx new file mode 100644 index 000000000..7d3880b9e --- /dev/null +++ b/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx @@ -0,0 +1,61 @@ +import { Card, Details, RLink, RStack, RText, YStack } from '@packrat/ui'; +import React, { type FC } from 'react'; +import { TripImage } from './TripImage'; +import { CreatedAtLabel, type FeedCardProps } from 'app/modules/feed'; +import { DuplicateIcon } from 'app/assets/icons'; +import { type TripDetails } from 'modules/trip/model'; +import { LocationLabel } from '../LocationLabel'; + +interface TripCardProps extends FeedCardProps {} + +export const TripPrimaryCard: FC = (props) => { + const tripDetails = Object.entries(props.details) + .filter(([key]) => key !== 'description') + .map(([key, value]) => ({ + key, + label: key, + value, + })); + + return ( + } + subtitle={} + actions={ + + + + + + View owner + + + } + content={ + + {props.details.description} +
+ + } + type={props.cardType} + /> + ); +}; diff --git a/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx b/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx new file mode 100644 index 000000000..3fc497617 --- /dev/null +++ b/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx @@ -0,0 +1,38 @@ +import { Card, RStack } from '@packrat/ui'; +import React, { type FC } from 'react'; +import { TripImage } from './TripImage'; +import { + CreatedAtLabel, + FavoriteButton, + type FeedCardProps, +} from 'app/modules/feed'; +import { type TripDetails } from 'modules/trip/model'; + +interface TripCardProps extends FeedCardProps {} + +export const TripSecondaryCard: FC = (props) => { + return ( + + } + subtitle={} + actions={ + + { + e.stopPropagation(); + e.preventDefault(); + props.toggleFavorite(); + }} + /> + + } + type={props.cardType} + /> + ); +}; diff --git a/packages/app/modules/trip/components/TripCard/index.ts b/packages/app/modules/trip/components/TripCard/index.ts new file mode 100644 index 000000000..0f807611f --- /dev/null +++ b/packages/app/modules/trip/components/TripCard/index.ts @@ -0,0 +1 @@ +export { TripCard } from './TripCard'; diff --git a/packages/app/modules/trip/components/index.ts b/packages/app/modules/trip/components/index.ts new file mode 100644 index 000000000..0f807611f --- /dev/null +++ b/packages/app/modules/trip/components/index.ts @@ -0,0 +1 @@ +export { TripCard } from './TripCard'; diff --git a/packages/app/modules/trip/index.ts b/packages/app/modules/trip/index.ts index fc736deaa..1183599be 100644 --- a/packages/app/modules/trip/index.ts +++ b/packages/app/modules/trip/index.ts @@ -1 +1,2 @@ export { useUserTrips } from './hooks'; +export { TripCard } from './components'; diff --git a/packages/app/modules/trip/model.ts b/packages/app/modules/trip/model.ts new file mode 100644 index 000000000..56ffc01d1 --- /dev/null +++ b/packages/app/modules/trip/model.ts @@ -0,0 +1,6 @@ +export interface TripDetails { + startDate: string; + endDate: string; + description: string; + destination: string; +} diff --git a/packages/app/screens/trip/createTrip.tsx b/packages/app/screens/trip/createTrip.tsx index 8c3314e2c..09a12fe8f 100644 --- a/packages/app/screens/trip/createTrip.tsx +++ b/packages/app/screens/trip/createTrip.tsx @@ -18,7 +18,6 @@ import { TripDateRangeCard, } from 'app/components/trip/TripCards'; import { WeatherData } from 'app/components/weather/WeatherData'; -import { disableScreen } from 'app/hoc/disableScreen'; function Trips() { const styles = useCustomStyles(loadStyles); @@ -96,4 +95,4 @@ const loadStyles = () => ({ }, }); -export default disableScreen(Trips); +export default Trips; From 5964892e210eb3f62447d0eb7633b20cc48e7faf Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Sun, 8 Sep 2024 05:39:58 +0100 Subject: [PATCH 020/127] =?UTF-8?q?=E2=9C=A8=20finalize=20access=20&=20ref?= =?UTF-8?q?resh=20token=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/config/trpcAxiosClient.ts | 16 ++++++++++++---- server/wrangler.toml.example | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/app/config/trpcAxiosClient.ts b/packages/app/config/trpcAxiosClient.ts index c34a85a67..a982277e1 100644 --- a/packages/app/config/trpcAxiosClient.ts +++ b/packages/app/config/trpcAxiosClient.ts @@ -5,6 +5,7 @@ import { getErrorMessageFromError } from 'app/utils/apiUtils'; import { Storage } from 'app/utils/storage'; import { vanillaTrpcClient } from 'app/trpc'; import { TRPCErrorResponse } from '@trpc/server/rpc'; +import { TRPCClientError } from '@trpc/client'; const REQUESTS_TO_SKIP_SUCCESS_MESSAGE = [ 'getMe', @@ -41,8 +42,14 @@ const responseInterceptor = (response: AxiosResponse) => { const responseErrorInterceptor = async ( error: AxiosError, ) => { - if (error?.response?.data[0]?.error?.data?.httpStatus === 401) { - // TODO: handle non batch links + const data = error?.response?.data; + const isUnauthorized = (item) => item?.error?.data?.httpStatus === 401; + // check auth error in both single or multiple objects response + const hasUnauthorizedError = Array.isArray(data) + ? data.some(isUnauthorized) + : isUnauthorized(data); + + if (data && hasUnauthorizedError) { const refreshToken = await Storage.getItem('refreshToken'); if (!refreshToken) return; // user is logged out if refreshToken isn't present @@ -57,8 +64,9 @@ const responseErrorInterceptor = async ( error.config.headers.Authorization = 'Bearer ' + tokens.accessToken; return await axios.request(error.config); } catch (error) { - // refreshToken has probably also expired. logout user. - logoutAuthUser(); + // refreshToken has also expired. Logout user. + if (error instanceof TRPCClientError && error.data.code == 'UNAUTHORIZED') + logoutAuthUser(); return error; } } diff --git a/server/wrangler.toml.example b/server/wrangler.toml.example index 475804b00..7223a0997 100644 --- a/server/wrangler.toml.example +++ b/server/wrangler.toml.example @@ -25,6 +25,7 @@ GOOGLE_CLIENT_SECRET="" STMP_EMAIL="" STMP_PASSWORD="" JWT_SECRET="" +REFRESH_TOKEN_SECRET="" SEND_GRID_API_KEY="" MAPBOX_ACCESS_TOKEN="" OSM_URI="" @@ -68,6 +69,7 @@ GOOGLE_CLIENT_SECRET="" STMP_EMAIL="" STMP_PASSWORD="" JWT_SECRET="" +REFRESH_TOKEN_SECRET="" SEND_GRID_API_KEY="" MAPBOX_ACCESS_TOKEN="" OSM_URI="" From 4d783a05d6d954a2557d2591cf0eee04cd2dd471 Mon Sep 17 00:00:00 2001 From: Taron Date: Sun, 8 Sep 2024 13:21:47 +0400 Subject: [PATCH 021/127] normalize trip data --- .../trip/TripCards/TripActivityCard.tsx | 6 +- .../trip/TripCards/TripPlaceCard.tsx | 22 +- packages/app/hooks/parks/index.ts | 8 +- packages/app/hooks/trails/index.ts | 11 +- packages/app/hooks/trips/useCreateTripForm.ts | 2 +- .../feed/components/FeedCard/FeedCard.tsx | 1 - .../feed/widgets/FeedPreview/FeedPreview.tsx | 2 +- .../components/TripCard/TripSecondaryCard.tsx | 26 +- packages/app/screens/trip/createTrip.tsx | 12 +- .../app/screens/trip/createTripStore/store.ts | 2 +- packages/app/utils/tripUtils.ts | 1 + .../tripRoutesValidator.ts | 25 +- .../migrations/0004_strange_king_bedlam.sql | 3 + server/migrations/0005_old_venom.sql | 5 + server/migrations/0006_foamy_clea.sql | 1 + server/migrations/meta/0004_snapshot.json | 1206 +++++++++++++++++ server/migrations/meta/0005_snapshot.json | 1185 ++++++++++++++++ server/migrations/meta/0006_snapshot.json | 1193 ++++++++++++++++ server/migrations/meta/_journal.json | 21 + server/src/db/schema.ts | 33 +- server/src/drizzle/methods/Geojson.ts | 4 +- server/src/services/trip/addTripService.ts | 23 +- .../src/services/trip/getTripByIdService.ts | 6 +- 23 files changed, 3696 insertions(+), 102 deletions(-) create mode 100644 server/migrations/0004_strange_king_bedlam.sql create mode 100644 server/migrations/0005_old_venom.sql create mode 100644 server/migrations/0006_foamy_clea.sql create mode 100644 server/migrations/meta/0004_snapshot.json create mode 100644 server/migrations/meta/0005_snapshot.json create mode 100644 server/migrations/meta/0006_snapshot.json diff --git a/packages/app/components/trip/TripCards/TripActivityCard.tsx b/packages/app/components/trip/TripCards/TripActivityCard.tsx index 0e7d5f1ab..0dd49201f 100644 --- a/packages/app/components/trip/TripCards/TripActivityCard.tsx +++ b/packages/app/components/trip/TripCards/TripActivityCard.tsx @@ -1,5 +1,8 @@ import React from 'react'; import useTheme from 'app/hooks/useTheme'; +import { TripCardBase } from './TripCardBase'; +import { getEnumValues } from 'app/utils/getEnumValues'; +import { formatTripActivityLabel } from 'app/utils/tripUtils'; import { FontAwesome5 } from '@expo/vector-icons'; import { Select as OriginalSelect } from '@packrat/ui'; @@ -17,9 +20,6 @@ enum TripActivity { } const Select: any = OriginalSelect; -import { TripCardBase } from './TripCardBase'; -import { getEnumValues } from 'app/utils/getEnumValues'; -import { formatTripActivityLabel } from 'app/utils/tripUtils'; const ActivityOptions = getEnumValues(TripActivity).map((activity) => ({ label: formatTripActivityLabel(activity.toString()), diff --git a/packages/app/components/trip/TripCards/TripPlaceCard.tsx b/packages/app/components/trip/TripCards/TripPlaceCard.tsx index b19ec34e6..ae04f818c 100644 --- a/packages/app/components/trip/TripCards/TripPlaceCard.tsx +++ b/packages/app/components/trip/TripCards/TripPlaceCard.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import useTheme from 'app/hooks/useTheme'; import { theme } from 'app/theme'; import { TripCardBase } from './TripCardBase'; @@ -13,20 +14,20 @@ import Carousel from 'app/components/carousel'; const RCard: any = OrirginalRCard; const RParagraph: any = OriginalRParagraph; -type TripPlaceCardProps = { - data: string[]; +interface TripPlaceCardProps { + data: Array<{ id: string; name: string }>; onToggle: (place: string) => void; - selectedValue: string; + selectedValue: string[]; icon: React.FC; title: string; -}; +} const TripPlaceCard = ({ data, onToggle, icon, title, - selectedValue, + selectedValue = [], }: TripPlaceCardProps) => { const { isDark } = useTheme(); @@ -37,11 +38,14 @@ const TripPlaceCard = ({ {data?.map((item) => { return ( { - onToggle(item); + onToggle(item.id); }} elevate bordered @@ -49,9 +53,9 @@ const TripPlaceCard = ({ > - {item} + {item.name} diff --git a/packages/app/hooks/parks/index.ts b/packages/app/hooks/parks/index.ts index fb772b5d9..2cf129775 100644 --- a/packages/app/hooks/parks/index.ts +++ b/packages/app/hooks/parks/index.ts @@ -2,9 +2,15 @@ import { queryTrpc } from '../../trpc'; interface Park { + id: string; properties: Record; } +interface ParkDetails { + id: string; + name: string; +} + function useParks({ latLng, radius = 5000 }) { // const { data, error, isLoading } = await trpc.getParksOSM.query({ // lat, @@ -32,7 +38,7 @@ function useParks({ latLng, radius = 5000 }) { (park) => park.properties && park.properties?.name, // && park.properties.name !== selectedSearch, ) - .map((park) => park.properties && park.properties?.name) + .map((park) => ({ id: park.id, name: park.properties?.name })) .filter(Boolean) .slice(0, 25); diff --git a/packages/app/hooks/trails/index.ts b/packages/app/hooks/trails/index.ts index 22c501686..a7e1ac2dc 100644 --- a/packages/app/hooks/trails/index.ts +++ b/packages/app/hooks/trails/index.ts @@ -2,7 +2,12 @@ import { queryTrpc } from '../../trpc'; interface Trail { properties: Record; - // other properties if needed + id: string; +} + +interface TrailDetails { + id: string; + name: string; } function useTrails({ latLng, selectedSearch, radius = 1000 }) { @@ -28,7 +33,7 @@ function useTrails({ latLng, selectedSearch, radius = 1000 }) { ); // React.useEffect(() => { - let filteredTrails: string[] = []; + let filteredTrails: TrailDetails[] = []; if (data) { const trails = data.features as Trail[]; filteredTrails = trails @@ -36,7 +41,7 @@ function useTrails({ latLng, selectedSearch, radius = 1000 }) { (trail) => trail.properties?.name && trail.properties.name !== selectedSearch, ) - .map((trail) => trail.properties?.name) + .map((trail) => ({ id: trail.id, name: trail.properties?.name })) .slice(0, 25); // Dispatching directly using the imported store diff --git a/packages/app/hooks/trips/useCreateTripForm.ts b/packages/app/hooks/trips/useCreateTripForm.ts index e1e2f0807..4f6df7488 100644 --- a/packages/app/hooks/trips/useCreateTripForm.ts +++ b/packages/app/hooks/trips/useCreateTripForm.ts @@ -17,7 +17,7 @@ export const useCreateTripForm = (currentDestination, photonDetails) => { formatCreateTripValuesForAPI, ); - const togglePlace = (name: 'trail' | 'park', value: any) => { + const togglePlace = (name: 'trails' | 'parks', value: any) => { setTripValue(name, store[name] !== value ? value : ''); }; diff --git a/packages/app/modules/feed/components/FeedCard/FeedCard.tsx b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx index 58098b2ac..7385a5adb 100644 --- a/packages/app/modules/feed/components/FeedCard/FeedCard.tsx +++ b/packages/app/modules/feed/components/FeedCard/FeedCard.tsx @@ -30,7 +30,6 @@ export const FeedCard: FC = ({ item, cardType, feedType }) => { typeof convertersByType[feedType] === 'function' ? convertersByType[feedType](item, user?.id) : null; - const handleAddToFavorite = () => { if (!user) return; const data = { diff --git a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx index 02723e320..f9c572006 100644 --- a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx +++ b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx @@ -28,7 +28,7 @@ const FeedPreviewScroll: React.FC = ({ const linkStr = `/pack/${item.id}`; return linkStr ? ( - + ) : null; })} diff --git a/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx b/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx index 3fc497617..8b189b4f0 100644 --- a/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx +++ b/packages/app/modules/trip/components/TripCard/TripSecondaryCard.tsx @@ -1,12 +1,9 @@ -import { Card, RStack } from '@packrat/ui'; +import { Card } from '@packrat/ui'; import React, { type FC } from 'react'; import { TripImage } from './TripImage'; -import { - CreatedAtLabel, - FavoriteButton, - type FeedCardProps, -} from 'app/modules/feed'; +import { type FeedCardProps } from 'app/modules/feed'; import { type TripDetails } from 'modules/trip/model'; +import { LocationLabel } from '../LocationLabel'; interface TripCardProps extends FeedCardProps {} @@ -14,24 +11,11 @@ export const TripSecondaryCard: FC = (props) => { return ( } - subtitle={} - actions={ - - { - e.stopPropagation(); - e.preventDefault(); - props.toggleFavorite(); - }} - /> - - } + subtitle={} type={props.cardType} /> ); diff --git a/packages/app/screens/trip/createTrip.tsx b/packages/app/screens/trip/createTrip.tsx index 09a12fe8f..95119d577 100644 --- a/packages/app/screens/trip/createTrip.tsx +++ b/packages/app/screens/trip/createTrip.tsx @@ -50,18 +50,18 @@ function Trips() { {latLng ? : null} togglePlace('trail', trail)} - selectedValue={tripStore.trail} + onToggle={(trail) => togglePlace('trails', trail)} + selectedValue={tripStore.trails} /> togglePlace('park', park)} - selectedValue={tripStore.park} + onToggle={(park) => togglePlace('parks', park)} + selectedValue={tripStore.parks} /> setTripValue('type', activity)} + selectedValue={tripStore.activity} + onChange={(activity) => setTripValue('activity', activity)} /> > = { - type: 'trip', + activity: 'trip', }; export const createTripReducer = ( diff --git a/packages/app/utils/tripUtils.ts b/packages/app/utils/tripUtils.ts index 0f3c42083..f138892e5 100644 --- a/packages/app/utils/tripUtils.ts +++ b/packages/app/utils/tripUtils.ts @@ -8,6 +8,7 @@ export const formatCreateTripValuesForAPI = (values: any) => { start_date: format(values?.start_date, 'MM/dd/yyyy'), end_date: format(values?.end_date, 'MM/dd/yyyy'), duration: JSON.stringify(values.duration), + geoJSON: JSON.stringify(values.geoJSON), weather: JSON.stringify(values.weather), }; }; diff --git a/packages/validations/src/validations/tripRoutesValidator/tripRoutesValidator.ts b/packages/validations/src/validations/tripRoutesValidator/tripRoutesValidator.ts index 5b9d895c6..3168a69c7 100644 --- a/packages/validations/src/validations/tripRoutesValidator/tripRoutesValidator.ts +++ b/packages/validations/src/validations/tripRoutesValidator/tripRoutesValidator.ts @@ -30,12 +30,13 @@ const featurePropertiesSchema = z.record( z.union([z.string(), z.number(), z.boolean()]), ); -const featureSchema = z.object({ - type: z.literal('Feature'), - id: z.string(), - properties: featurePropertiesSchema, - geometry: geometrySchema, -}); +// TODO ADD MAPS +// const featureSchema = z.object({ +// type: z.literal('Feature'), +// id: z.string(), +// properties: featurePropertiesSchema, +// geometry: geometrySchema, +// }); export const getTrips = z.object({ owner_id: z.string().optional(), @@ -46,17 +47,13 @@ export const getTripById = z.object({ }); export const addTripDetails = z.object({ - duration: z.string(), start_date: z.string(), end_date: z.string(), destination: z.string(), - type: z.enum(tripActivityValues), - park: z.string().optional(), - trail: z.string().optional(), - geoJSON: z.object({ - type: z.literal('FeatureCollection'), - features: z.array(featureSchema), - }), + activity: z.enum(tripActivityValues), + parks: z.string().optional(), + trails: z.string().optional(), + geoJSON: z.string(), owner_id: z.string(), pack_id: z.string(), }); diff --git a/server/migrations/0004_strange_king_bedlam.sql b/server/migrations/0004_strange_king_bedlam.sql new file mode 100644 index 000000000..91614ffc0 --- /dev/null +++ b/server/migrations/0004_strange_king_bedlam.sql @@ -0,0 +1,3 @@ +ALTER TABLE trip ADD `parks` text;--> statement-breakpoint +ALTER TABLE `trip` DROP COLUMN `duration`;--> statement-breakpoint +ALTER TABLE `trip` DROP COLUMN `weather`; \ No newline at end of file diff --git a/server/migrations/0005_old_venom.sql b/server/migrations/0005_old_venom.sql new file mode 100644 index 000000000..29848e7f6 --- /dev/null +++ b/server/migrations/0005_old_venom.sql @@ -0,0 +1,5 @@ +ALTER TABLE geojson ADD `geoJSON` text;--> statement-breakpoint +ALTER TABLE `geojson` DROP COLUMN `type`;--> statement-breakpoint +ALTER TABLE `geojson` DROP COLUMN `geo_json_id`;--> statement-breakpoint +ALTER TABLE `geojson` DROP COLUMN `properties`;--> statement-breakpoint +ALTER TABLE `geojson` DROP COLUMN `geometry`; \ No newline at end of file diff --git a/server/migrations/0006_foamy_clea.sql b/server/migrations/0006_foamy_clea.sql new file mode 100644 index 000000000..f0d4b3809 --- /dev/null +++ b/server/migrations/0006_foamy_clea.sql @@ -0,0 +1 @@ +ALTER TABLE trip ADD `activity` text DEFAULT 'trip'; \ No newline at end of file diff --git a/server/migrations/meta/0004_snapshot.json b/server/migrations/meta/0004_snapshot.json new file mode 100644 index 000000000..e5b3fb405 --- /dev/null +++ b/server/migrations/meta/0004_snapshot.json @@ -0,0 +1,1206 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "e683aa5a-5070-4d5b-a2bf-4e2ef064106e", + "prevId": "57e2d274-a728-4d09-9aff-74d3b92399fa", + "tables": { + "conversation": { + "name": "conversation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "itemTypeId": { + "name": "itemTypeId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "history": { + "name": "history", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "geojson": { + "name": "geojson", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "geo_json_id": { + "name": "geo_json_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "properties": { + "name": "properties", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geometry": { + "name": "geometry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "global": { + "name": "global", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "item_category_id_item_category_id_fk": { + "name": "item_category_id_item_category_id_fk", + "tableFrom": "item", + "tableTo": "item_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "item_owner_id_user_id_fk": { + "name": "item_owner_id_user_id_fk", + "tableFrom": "item", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_category": { + "name": "item_category", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_owners": { + "name": "item_owners", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_owners_item_id_item_id_fk": { + "name": "item_owners_item_id_item_id_fk", + "tableFrom": "item_owners", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_owners_owner_id_user_id_fk": { + "name": "item_owners_owner_id_user_id_fk", + "tableFrom": "item_owners", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "owner_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "item_packs": { + "name": "item_packs", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_packs_item_id_item_id_fk": { + "name": "item_packs_item_id_item_id_fk", + "tableFrom": "item_packs", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_packs_pack_id_pack_id_fk": { + "name": "item_packs_pack_id_pack_id_fk", + "tableFrom": "item_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "pack_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "node": { + "name": "node", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lat": { + "name": "lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lon": { + "name": "lon", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pack": { + "name": "pack", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "grades": { + "name": "grades", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weight\":\"\",\"essentialItems\":\"\",\"redundancyAndVersatility\":\"\"}'" + }, + "scores": { + "name": "scores", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weightScore\":0,\"essentialItemsScore\":0,\"redundancyAndVersatilityScore\":0}'" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'pack'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "pack_owner_id_user_id_fk": { + "name": "pack_owner_id_user_id_fk", + "tableFrom": "pack", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "relation": { + "name": "relation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'relation'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "template": { + "name": "template", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pack'" + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_global_template": { + "name": "is_global_template", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "template_created_by_user_id_fk": { + "name": "template_created_by_user_id_fk", + "tableFrom": "template", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip": { + "name": "trip", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parks": { + "name": "parks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "packs_id": { + "name": "packs_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'trip'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "trip_owner_id_user_id_fk": { + "name": "trip_owner_id_user_id_fk", + "tableFrom": "trip", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trip_packs_id_pack_id_fk": { + "name": "trip_packs_id_pack_id_fk", + "tableFrom": "trip", + "tableTo": "pack", + "columnsFrom": [ + "packs_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip_geojsons": { + "name": "trip_geojsons", + "columns": { + "trip_id": { + "name": "trip_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geojson_id": { + "name": "geojson_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "trip_geojsons_trip_id_trip_id_fk": { + "name": "trip_geojsons_trip_id_trip_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "trip", + "columnsFrom": [ + "trip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "trip_geojsons_geojson_id_geojson_id_fk": { + "name": "trip_geojsons_geojson_id_geojson_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "geojson", + "columnsFrom": [ + "geojson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "geojson_id", + "trip_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_certified_guide": { + "name": "is_certified_guide", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token": { + "name": "password_reset_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token_expiration": { + "name": "password_reset_token_expiration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "offline_maps": { + "name": "offline_maps", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_image": { + "name": "profile_image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preferred_weather": { + "name": "preferred_weather", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'celsius'" + }, + "preferred_weight": { + "name": "preferred_weight", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'lb'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_favorite_packs": { + "name": "user_favorite_packs", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorite_packs_user_id_user_id_fk": { + "name": "user_favorite_packs_user_id_user_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_favorite_packs_pack_id_pack_id_fk": { + "name": "user_favorite_packs_pack_id_pack_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "pack_id", + "user_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "way": { + "name": "way", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "way_nodes": { + "name": "way_nodes", + "columns": { + "way_id": { + "name": "way_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "way_nodes_way_id_way_id_fk": { + "name": "way_nodes_way_id_way_id_fk", + "tableFrom": "way_nodes", + "tableTo": "way", + "columnsFrom": [ + "way_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "way_nodes_node_id_node_id_fk": { + "name": "way_nodes_node_id_node_id_fk", + "tableFrom": "way_nodes", + "tableTo": "node", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "node_id", + "way_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/server/migrations/meta/0005_snapshot.json b/server/migrations/meta/0005_snapshot.json new file mode 100644 index 000000000..02091f781 --- /dev/null +++ b/server/migrations/meta/0005_snapshot.json @@ -0,0 +1,1185 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "426b422b-7eb7-40b4-9fc2-3ec3833964bd", + "prevId": "e683aa5a-5070-4d5b-a2bf-4e2ef064106e", + "tables": { + "conversation": { + "name": "conversation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "itemTypeId": { + "name": "itemTypeId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "history": { + "name": "history", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "geojson": { + "name": "geojson", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "geoJSON": { + "name": "geoJSON", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "global": { + "name": "global", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "item_category_id_item_category_id_fk": { + "name": "item_category_id_item_category_id_fk", + "tableFrom": "item", + "tableTo": "item_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "item_owner_id_user_id_fk": { + "name": "item_owner_id_user_id_fk", + "tableFrom": "item", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_category": { + "name": "item_category", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_owners": { + "name": "item_owners", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_owners_item_id_item_id_fk": { + "name": "item_owners_item_id_item_id_fk", + "tableFrom": "item_owners", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_owners_owner_id_user_id_fk": { + "name": "item_owners_owner_id_user_id_fk", + "tableFrom": "item_owners", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "owner_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "item_packs": { + "name": "item_packs", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_packs_item_id_item_id_fk": { + "name": "item_packs_item_id_item_id_fk", + "tableFrom": "item_packs", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_packs_pack_id_pack_id_fk": { + "name": "item_packs_pack_id_pack_id_fk", + "tableFrom": "item_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "pack_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "node": { + "name": "node", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lat": { + "name": "lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lon": { + "name": "lon", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pack": { + "name": "pack", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "grades": { + "name": "grades", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weight\":\"\",\"essentialItems\":\"\",\"redundancyAndVersatility\":\"\"}'" + }, + "scores": { + "name": "scores", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weightScore\":0,\"essentialItemsScore\":0,\"redundancyAndVersatilityScore\":0}'" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'pack'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "pack_owner_id_user_id_fk": { + "name": "pack_owner_id_user_id_fk", + "tableFrom": "pack", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "relation": { + "name": "relation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'relation'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "template": { + "name": "template", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pack'" + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_global_template": { + "name": "is_global_template", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "template_created_by_user_id_fk": { + "name": "template_created_by_user_id_fk", + "tableFrom": "template", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip": { + "name": "trip", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parks": { + "name": "parks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "packs_id": { + "name": "packs_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'trip'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "trip_owner_id_user_id_fk": { + "name": "trip_owner_id_user_id_fk", + "tableFrom": "trip", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trip_packs_id_pack_id_fk": { + "name": "trip_packs_id_pack_id_fk", + "tableFrom": "trip", + "tableTo": "pack", + "columnsFrom": [ + "packs_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip_geojsons": { + "name": "trip_geojsons", + "columns": { + "trip_id": { + "name": "trip_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geojson_id": { + "name": "geojson_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "trip_geojsons_trip_id_trip_id_fk": { + "name": "trip_geojsons_trip_id_trip_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "trip", + "columnsFrom": [ + "trip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "trip_geojsons_geojson_id_geojson_id_fk": { + "name": "trip_geojsons_geojson_id_geojson_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "geojson", + "columnsFrom": [ + "geojson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "geojson_id", + "trip_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_certified_guide": { + "name": "is_certified_guide", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token": { + "name": "password_reset_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token_expiration": { + "name": "password_reset_token_expiration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "offline_maps": { + "name": "offline_maps", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_image": { + "name": "profile_image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preferred_weather": { + "name": "preferred_weather", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'celsius'" + }, + "preferred_weight": { + "name": "preferred_weight", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'lb'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_favorite_packs": { + "name": "user_favorite_packs", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorite_packs_user_id_user_id_fk": { + "name": "user_favorite_packs_user_id_user_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_favorite_packs_pack_id_pack_id_fk": { + "name": "user_favorite_packs_pack_id_pack_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "pack_id", + "user_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "way": { + "name": "way", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "way_nodes": { + "name": "way_nodes", + "columns": { + "way_id": { + "name": "way_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "way_nodes_way_id_way_id_fk": { + "name": "way_nodes_way_id_way_id_fk", + "tableFrom": "way_nodes", + "tableTo": "way", + "columnsFrom": [ + "way_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "way_nodes_node_id_node_id_fk": { + "name": "way_nodes_node_id_node_id_fk", + "tableFrom": "way_nodes", + "tableTo": "node", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "node_id", + "way_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/server/migrations/meta/0006_snapshot.json b/server/migrations/meta/0006_snapshot.json new file mode 100644 index 000000000..9f5a814cf --- /dev/null +++ b/server/migrations/meta/0006_snapshot.json @@ -0,0 +1,1193 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "9e58125e-cf76-41f3-8680-7fac53f591d7", + "prevId": "426b422b-7eb7-40b4-9fc2-3ec3833964bd", + "tables": { + "conversation": { + "name": "conversation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "itemTypeId": { + "name": "itemTypeId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "history": { + "name": "history", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "geojson": { + "name": "geojson", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "geoJSON": { + "name": "geoJSON", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "global": { + "name": "global", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "item_category_id_item_category_id_fk": { + "name": "item_category_id_item_category_id_fk", + "tableFrom": "item", + "tableTo": "item_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "item_owner_id_user_id_fk": { + "name": "item_owner_id_user_id_fk", + "tableFrom": "item", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_category": { + "name": "item_category", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item_owners": { + "name": "item_owners", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_owners_item_id_item_id_fk": { + "name": "item_owners_item_id_item_id_fk", + "tableFrom": "item_owners", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_owners_owner_id_user_id_fk": { + "name": "item_owners_owner_id_user_id_fk", + "tableFrom": "item_owners", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "owner_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "item_packs": { + "name": "item_packs", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "item_packs_item_id_item_id_fk": { + "name": "item_packs_item_id_item_id_fk", + "tableFrom": "item_packs", + "tableTo": "item", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_packs_pack_id_pack_id_fk": { + "name": "item_packs_pack_id_pack_id_fk", + "tableFrom": "item_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "item_id", + "pack_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "node": { + "name": "node", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lat": { + "name": "lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lon": { + "name": "lon", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pack": { + "name": "pack", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "grades": { + "name": "grades", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weight\":\"\",\"essentialItems\":\"\",\"redundancyAndVersatility\":\"\"}'" + }, + "scores": { + "name": "scores", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"weightScore\":0,\"essentialItemsScore\":0,\"redundancyAndVersatilityScore\":0}'" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'pack'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "pack_owner_id_user_id_fk": { + "name": "pack_owner_id_user_id_fk", + "tableFrom": "pack", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "relation": { + "name": "relation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'relation'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "template": { + "name": "template", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pack'" + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_global_template": { + "name": "is_global_template", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "template_created_by_user_id_fk": { + "name": "template_created_by_user_id_fk", + "tableFrom": "template", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip": { + "name": "trip", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parks": { + "name": "parks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "packs_id": { + "name": "packs_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'trip'" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'trip'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "trip_owner_id_user_id_fk": { + "name": "trip_owner_id_user_id_fk", + "tableFrom": "trip", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trip_packs_id_pack_id_fk": { + "name": "trip_packs_id_pack_id_fk", + "tableFrom": "trip", + "tableTo": "pack", + "columnsFrom": [ + "packs_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trip_geojsons": { + "name": "trip_geojsons", + "columns": { + "trip_id": { + "name": "trip_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geojson_id": { + "name": "geojson_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "trip_geojsons_trip_id_trip_id_fk": { + "name": "trip_geojsons_trip_id_trip_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "trip", + "columnsFrom": [ + "trip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "trip_geojsons_geojson_id_geojson_id_fk": { + "name": "trip_geojsons_geojson_id_geojson_id_fk", + "tableFrom": "trip_geojsons", + "tableTo": "geojson", + "columnsFrom": [ + "geojson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "geojson_id", + "trip_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_certified_guide": { + "name": "is_certified_guide", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token": { + "name": "password_reset_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_reset_token_expiration": { + "name": "password_reset_token_expiration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "offline_maps": { + "name": "offline_maps", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_image": { + "name": "profile_image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preferred_weather": { + "name": "preferred_weather", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'celsius'" + }, + "preferred_weight": { + "name": "preferred_weight", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'lb'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_favorite_packs": { + "name": "user_favorite_packs", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pack_id": { + "name": "pack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorite_packs_user_id_user_id_fk": { + "name": "user_favorite_packs_user_id_user_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_favorite_packs_pack_id_pack_id_fk": { + "name": "user_favorite_packs_pack_id_pack_id_fk", + "tableFrom": "user_favorite_packs", + "tableTo": "pack", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "pack_id", + "user_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + }, + "way": { + "name": "way", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "osm_id": { + "name": "osm_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "osm_type": { + "name": "osm_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "geo_json": { + "name": "geo_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "way_nodes": { + "name": "way_nodes", + "columns": { + "way_id": { + "name": "way_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "way_nodes_way_id_way_id_fk": { + "name": "way_nodes_way_id_way_id_fk", + "tableFrom": "way_nodes", + "tableTo": "way", + "columnsFrom": [ + "way_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "way_nodes_node_id_node_id_fk": { + "name": "way_nodes_node_id_node_id_fk", + "tableFrom": "way_nodes", + "tableTo": "node", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "id": { + "columns": [ + "node_id", + "way_id" + ], + "name": "id" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/server/migrations/meta/_journal.json b/server/migrations/meta/_journal.json index 9e5b092be..de0014642 100644 --- a/server/migrations/meta/_journal.json +++ b/server/migrations/meta/_journal.json @@ -29,6 +29,27 @@ "when": 1721509132975, "tag": "0003_reflective_mimic", "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1725739824948, + "tag": "0004_strange_king_bedlam", + "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1725747366707, + "tag": "0005_old_venom", + "breakpoints": true + }, + { + "idx": 6, + "version": "5", + "when": 1725786532593, + "tag": "0006_foamy_clea", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 4ff7ce3f2..04574ddb2 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -307,8 +307,12 @@ export const trip = sqliteTable('trip', { .$defaultFn(() => createId()), name: text('name').notNull(), description: text('description').notNull(), - duration: text('duration').notNull(), - weather: text('weather').notNull(), + parks: text('parks', { mode: 'json' }).$type< + Array<{ id: string; name: string }> + >(), + trails: text('parks', { mode: 'json' }).$type< + Array<{ id: string; name: string }> + >(), start_date: text('start_date').notNull(), end_date: text('end_date').notNull(), destination: text('destination').notNull(), @@ -319,6 +323,7 @@ export const trip = sqliteTable('trip', { onDelete: 'set null', }), is_public: integer('is_public', { mode: 'boolean' }), + activity: text('activity').default('trip'), type: text('type').default('trip'), createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`), updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`), @@ -449,28 +454,16 @@ export const geojson = sqliteTable('geojson', { id: text('id') .primaryKey() .$defaultFn(() => createId()), - type: text('type').notNull(), // Regex - geojsonId: text('geo_json_id').notNull(), // Regex - properties: text('properties', { mode: 'json' }).$type(), - geometry: text('geometry', { mode: 'json' }) - .$type<{ - type: - | 'Point' - | 'LineString' - | 'Polygon' - | 'MultiPoint' - | 'MultiPolygon' - | 'MultiLineString'; - coordinates: Array; - }>() - .notNull(), + geoJSON: text('geoJSON').$type(), createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`), updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`), }); -export const geojsonRelations = relations(geojson, ({ many }) => ({ - trips: many(tripGeojsons), - tripGeojsons: many(tripGeojsons), +export const geojsonRelations = relations(geojson, ({ one }) => ({ + tripGeojson: one(tripGeojsons, { + fields: [geojson.id], + references: [tripGeojsons.geojsonId], + }), })); export const tripGeojsonsRelations = relations(tripGeojsons, ({ one }) => ({ diff --git a/server/src/drizzle/methods/Geojson.ts b/server/src/drizzle/methods/Geojson.ts index b9bc4f0e7..def55775c 100644 --- a/server/src/drizzle/methods/Geojson.ts +++ b/server/src/drizzle/methods/Geojson.ts @@ -2,12 +2,12 @@ import { DbClient } from '../../db/client'; import { type InsertGeoJson, geojson } from '../../db/schema'; export class GeoJson { - async create(geoJSONData: InsertGeoJson) { + async create(geoJSON: InsertGeoJson) { try { const db = DbClient.instance; const record = await db .insert(geojson) - .values(geoJSONData) + .values({ geoJSON }) .returning() .get(); return record; diff --git a/server/src/services/trip/addTripService.ts b/server/src/services/trip/addTripService.ts index b9e198ed1..842c8d895 100644 --- a/server/src/services/trip/addTripService.ts +++ b/server/src/services/trip/addTripService.ts @@ -6,29 +6,20 @@ import { validateGeojsonId, validateGeojsonType } from '../../utils/geojson'; export const addTripService = async (tripData: any) => { try { const { geoJSON, ...otherTripData } = tripData; + console.log({ tripData }); const tripClass = new Trip(); // Create Trip - const newTrip = await tripClass.create({ ...otherTripData, weather: 'w' }); // TODO remove not null from db + const newTrip = await tripClass.create(otherTripData); const geojsonClass = new GeoJson(); const tripGeoJsonClass = new TripGeoJson(); if (!geoJSON) { throw new Error("Geojson data doesn't exist"); } - geoJSON.features.map(async (feature) => { - const { id, type, properties, geometry } = feature; - if (!validateGeojsonId(id) || !validateGeojsonType(type)) { - throw new Error('Invalid geojson Id or geojson type'); - } - const insertedGeoJson = await geojsonClass.create({ - properties, - type, - geojsonId: id, - geometry, - }); - await tripGeoJsonClass.create({ - tripId: newTrip.id, - geojsonId: insertedGeoJson.id, - }); + + const insertedGeoJson = await geojsonClass.create(geoJSON); + await tripGeoJsonClass.create({ + tripId: newTrip.id, + geojsonId: insertedGeoJson.id, }); return newTrip; diff --git a/server/src/services/trip/getTripByIdService.ts b/server/src/services/trip/getTripByIdService.ts index acc63bcf2..fd7d9b748 100644 --- a/server/src/services/trip/getTripByIdService.ts +++ b/server/src/services/trip/getTripByIdService.ts @@ -17,12 +17,12 @@ export const getTripByIdService = async (tripId: string): Promise => { ...trip.packs, items: trip.packs?.itemPacks?.map((itemPack) => itemPack.item), }; - const geojsonData = trip.tripGeojsons?.map( + const geoJSON = trip.tripGeojsons?.map( (tripGeojson) => tripGeojson.geojson, - ); + )[0]; return { ...trip, - geojson: { type: 'FeatureCollection', features: geojsonData }, + geoJSON: geoJSON?.geoJSON ? JSON.parse(geoJSON?.geoJSON) : null, packs, }; } catch (error) { From c934de646be9819d5bc10277567d194ee6c07759 Mon Sep 17 00:00:00 2001 From: Taron Date: Sun, 8 Sep 2024 15:47:46 +0400 Subject: [PATCH 022/127] fix trip places --- .../app/components/trip/TripCards/TripPlaceCard.tsx | 4 ++-- packages/app/hooks/trips/useCreateTripForm.ts | 10 +++++++++- packages/app/screens/trip/createTrip.tsx | 4 ++-- packages/app/utils/tripUtils.ts | 3 ++- server/src/services/trip/addTripService.ts | 6 +++++- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/app/components/trip/TripCards/TripPlaceCard.tsx b/packages/app/components/trip/TripCards/TripPlaceCard.tsx index ae04f818c..23ab7ee9e 100644 --- a/packages/app/components/trip/TripCards/TripPlaceCard.tsx +++ b/packages/app/components/trip/TripCards/TripPlaceCard.tsx @@ -16,7 +16,7 @@ const RParagraph: any = OriginalRParagraph; interface TripPlaceCardProps { data: Array<{ id: string; name: string }>; - onToggle: (place: string) => void; + onToggle: (place: { id: string; name: string }) => void; selectedValue: string[]; icon: React.FC; title: string; @@ -45,7 +45,7 @@ const TripPlaceCard = ({ : null } onPress={() => { - onToggle(item.id); + onToggle(item); }} elevate bordered diff --git a/packages/app/hooks/trips/useCreateTripForm.ts b/packages/app/hooks/trips/useCreateTripForm.ts index 4f6df7488..b9370d915 100644 --- a/packages/app/hooks/trips/useCreateTripForm.ts +++ b/packages/app/hooks/trips/useCreateTripForm.ts @@ -18,7 +18,15 @@ export const useCreateTripForm = (currentDestination, photonDetails) => { ); const togglePlace = (name: 'trails' | 'parks', value: any) => { - setTripValue(name, store[name] !== value ? value : ''); + const currentPlaces = store[name] || []; + + const placeExists = currentPlaces.some((place) => place.id === value.id); + + const updatedPlaces = placeExists + ? currentPlaces.filter((place) => place.id !== value.id) + : [...currentPlaces, value]; + + setTripValue(name, updatedPlaces); }; const createTripFormValues = useMemo>>( diff --git a/packages/app/screens/trip/createTrip.tsx b/packages/app/screens/trip/createTrip.tsx index 95119d577..94d835545 100644 --- a/packages/app/screens/trip/createTrip.tsx +++ b/packages/app/screens/trip/createTrip.tsx @@ -51,12 +51,12 @@ function Trips() { togglePlace('trails', trail)} - selectedValue={tripStore.trails} + selectedValue={tripStore.trails?.map?.(({ id }) => id) || []} /> togglePlace('parks', park)} - selectedValue={tripStore.parks} + selectedValue={tripStore.parks?.map?.(({ id }) => id) || []} /> { end_date: format(values?.end_date, 'MM/dd/yyyy'), duration: JSON.stringify(values.duration), geoJSON: JSON.stringify(values.geoJSON), - weather: JSON.stringify(values.weather), + trails: JSON.stringify(values.trails), + parks: JSON.stringify(values.parks), }; }; diff --git a/server/src/services/trip/addTripService.ts b/server/src/services/trip/addTripService.ts index 842c8d895..884d59408 100644 --- a/server/src/services/trip/addTripService.ts +++ b/server/src/services/trip/addTripService.ts @@ -9,7 +9,11 @@ export const addTripService = async (tripData: any) => { console.log({ tripData }); const tripClass = new Trip(); // Create Trip - const newTrip = await tripClass.create(otherTripData); + const newTrip = await tripClass.create({ + ...otherTripData, + trails: otherTripData.trails ? JSON.parse(otherTripData.trails) : null, + parks: otherTripData.parks ? JSON.parse(otherTripData.parks) : null, + }); const geojsonClass = new GeoJson(); const tripGeoJsonClass = new TripGeoJson(); if (!geoJSON) { From f3a080c643c9c5b48998b6a654a096758597cf46 Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Sun, 8 Sep 2024 20:33:40 +0530 Subject: [PATCH 023/127] Add activity to tripcard --- packages/app/modules/feed/components/FeedCard/utils.ts | 1 + packages/app/modules/feed/model.ts | 1 + .../app/modules/feed/widgets/FeedPreview/FeedPreview.tsx | 5 ++--- .../app/modules/trip/components/TripCard/TripPrimaryCard.tsx | 5 +++++ packages/app/modules/trip/model.ts | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/app/modules/feed/components/FeedCard/utils.ts b/packages/app/modules/feed/components/FeedCard/utils.ts index 1162372cb..90158b801 100644 --- a/packages/app/modules/feed/components/FeedCard/utils.ts +++ b/packages/app/modules/feed/components/FeedCard/utils.ts @@ -67,6 +67,7 @@ export const feedItemTripCardConverter: Converter< description: truncateString(input.description, 100), startDate: input.start_date, endDate: input.end_date, + activity: input.activity, }, favoriteCount: input.favorites_count, }; diff --git a/packages/app/modules/feed/model.ts b/packages/app/modules/feed/model.ts index 103f7b9d9..5e475165d 100644 --- a/packages/app/modules/feed/model.ts +++ b/packages/app/modules/feed/model.ts @@ -41,6 +41,7 @@ export interface TripFeedItem extends BaseFeedItem { duration: string; start_date: string; end_date: string; + activity: string; } export type FeedItem = PackFeedItem | TripFeedItem; diff --git a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx index f9c572006..beaed12b6 100644 --- a/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx +++ b/packages/app/modules/feed/widgets/FeedPreview/FeedPreview.tsx @@ -25,12 +25,11 @@ const FeedPreviewScroll: React.FC = ({ {feedData ?.filter((item): item is FeedItem => item.type !== null) .map((item: FeedItem) => { - const linkStr = `/pack/${item.id}`; - return linkStr ? ( + return ( - ) : null; + ); })} ); diff --git a/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx b/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx index 7d3880b9e..70f384fb1 100644 --- a/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx +++ b/packages/app/modules/trip/components/TripCard/TripPrimaryCard.tsx @@ -51,6 +51,11 @@ export const TripPrimaryCard: FC = (props) => { label: 'End Date', value: props.details.endDate, }, + { + key: 'activity', + label: 'Activity', + value: props.details.activity, + }, ]} /> diff --git a/packages/app/modules/trip/model.ts b/packages/app/modules/trip/model.ts index 56ffc01d1..b64a30cff 100644 --- a/packages/app/modules/trip/model.ts +++ b/packages/app/modules/trip/model.ts @@ -3,4 +3,5 @@ export interface TripDetails { endDate: string; description: string; destination: string; + activity: string; } From f8537ff37f6b02e560695acd768734e5afe7717e Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Sun, 8 Sep 2024 21:08:39 +0530 Subject: [PATCH 024/127] Improve visibilty in create trip --- .../trip/TripCards/TripPlaceCard.tsx | 13 +++++--- packages/app/screens/trip/createTrip.tsx | 30 +++++++++++-------- .../elements/datepickers/RangePicker.tsx | 3 ++ 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/app/components/trip/TripCards/TripPlaceCard.tsx b/packages/app/components/trip/TripCards/TripPlaceCard.tsx index 23ab7ee9e..dc49cfdf9 100644 --- a/packages/app/components/trip/TripCards/TripPlaceCard.tsx +++ b/packages/app/components/trip/TripCards/TripPlaceCard.tsx @@ -10,6 +10,7 @@ import { RStack, } from '@packrat/ui'; import Carousel from 'app/components/carousel'; +import { current } from 'immer'; const RCard: any = OrirginalRCard; const RParagraph: any = OriginalRParagraph; @@ -29,19 +30,19 @@ const TripPlaceCard = ({ title, selectedValue = [], }: TripPlaceCardProps) => { - const { isDark } = useTheme(); + const { currentTheme } = useTheme(); return ( - + {data?.map((item) => { return ( { @@ -53,7 +54,11 @@ const TripPlaceCard = ({ > {item.name} diff --git a/packages/app/screens/trip/createTrip.tsx b/packages/app/screens/trip/createTrip.tsx index 94d835545..71921d44f 100644 --- a/packages/app/screens/trip/createTrip.tsx +++ b/packages/app/screens/trip/createTrip.tsx @@ -81,18 +81,22 @@ function Trips() { ); } -const loadStyles = () => ({ - mutualStyles: { - backgroundColor: theme.colors.background, - flex: 1, - flexDirection: 'column', - height: '100%', - paddingBottom: 30, - }, - container: { - gap: 50, - padding: 20, - }, -}); +const loadStyles = () => { + const { currentTheme } = useTheme(); + + return { + mutualStyles: { + backgroundColor: currentTheme.colors.background, + flex: 1, + flexDirection: 'column', + height: '100%', + paddingBottom: 30, + }, + container: { + gap: 50, + padding: 20, + }, + }; +}; export default Trips; diff --git a/packages/ui/src/Bento/elements/datepickers/RangePicker.tsx b/packages/ui/src/Bento/elements/datepickers/RangePicker.tsx index fe159a998..2367791c6 100644 --- a/packages/ui/src/Bento/elements/datepickers/RangePicker.tsx +++ b/packages/ui/src/Bento/elements/datepickers/RangePicker.tsx @@ -21,6 +21,7 @@ import { } from './common/dateParts'; import { Platform } from 'react-native'; import { useDateAnimation } from './DatePicker'; +import useTheme from 'app/hooks/useTheme'; const RANGE_STYLE: { [key: string]: GetProps } = { 'in-range': { @@ -305,6 +306,7 @@ export function RangePicker({ setOpen(false); } }, [selectedDates]); + const { currentTheme } = useTheme(); // uncomment this to limit the range of dates // const M = now.getMonth() @@ -323,6 +325,7 @@ export function RangePicker({ }} onButtonPress={() => setOpen(true)} width={260} + color={currentTheme.colors.text} /> From 3835e117d8568b00a2fc38e298a1ab1429afd56e Mon Sep 17 00:00:00 2001 From: Anmol Verma Date: Sun, 8 Sep 2024 23:46:36 +0530 Subject: [PATCH 025/127] fix selected packs --- .../components/PackTable/TableHelperComponents.tsx | 4 ++-- packages/app/modules/pack/widgets/PackContainer.tsx | 11 ++++++++++- packages/ui/src/RSelect/index.tsx | 7 ++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/app/modules/pack/components/PackTable/TableHelperComponents.tsx b/packages/app/modules/pack/components/PackTable/TableHelperComponents.tsx index e6c3f0cbd..46142d4c9 100644 --- a/packages/app/modules/pack/components/PackTable/TableHelperComponents.tsx +++ b/packages/app/modules/pack/components/PackTable/TableHelperComponents.tsx @@ -128,10 +128,10 @@ const WeightUnitDropdown = ({ value, onChange }: WeightUnitDropdownProps) => { }} > onChange(itemValue)} - placeholder={`Select Weight unit : ${value}`} + placeholder={'Select Weight unit'} native={true} /> diff --git a/packages/app/modules/pack/widgets/PackContainer.tsx b/packages/app/modules/pack/widgets/PackContainer.tsx index 10bb5ecad..d7bbf7925 100644 --- a/packages/app/modules/pack/widgets/PackContainer.tsx +++ b/packages/app/modules/pack/widgets/PackContainer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { View } from 'react-native'; import { AddItemModal } from 'app/modules/item'; import useCustomStyles from 'app/hooks/useCustomStyles'; @@ -25,10 +25,19 @@ export default function PackContainer({ isCreatingTrip = false }) { refetch: refetchQuery, } = useUserPacks(user?.id); + const oldPacks = useRef([]).current; + useEffect(() => { refetchQuery(); }, [refetch]); + useEffect(() => { + if (packs.length > oldPacks.length) { + const newPack = packs.find((pack) => !oldPacks.includes(pack.id)); + setCurrentPackId(newPack?.id); + oldPacks.push(newPack?.id); + } + }, packs); /** * Handles the packing based on the given value. * diff --git a/packages/ui/src/RSelect/index.tsx b/packages/ui/src/RSelect/index.tsx index b772ae8fa..87c07017a 100644 --- a/packages/ui/src/RSelect/index.tsx +++ b/packages/ui/src/RSelect/index.tsx @@ -98,6 +98,11 @@ export function SelectItem(props) { const [position, setPosition] = useState(0); + const selectedItemLabel = useMemo(() => { + const label = data.find((i) => i[valueKey] === value)?.[textKey]; + return label || value; + }, [value]); + return (