diff --git a/.github/actions/find-or-create-comment/action.yml b/.github/actions/find-or-create-comment/action.yml new file mode 100644 index 000000000..5d435abb0 --- /dev/null +++ b/.github/actions/find-or-create-comment/action.yml @@ -0,0 +1,41 @@ +# Creates a comment to show native preview build status +# Or finds the comment if it exists +# Exposes the comment id in env in either case +name: Find or create comment +description: 'Finds the comment of build status or create one. Outputs the comment id.' + +inputs: + github-token: + description: 'Github token' + required: true + +runs: + using: 'composite' + steps: + - name: Find or create comment + uses: actions/github-script@v6 + with: + github-token: ${{ inputs.github-token }} + script: | + const buildName = '${{ env.build-name }}'; + const commentMagicPrefix = '${{ env.comment-unique-magic-prefix }}'; + const comments = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + const existingComment = comments.data.find(comment => comment.body.startsWith(commentMagicPrefix)); + + if (existingComment) { + core.exportVariable('comment_id', existingComment.id) + } else { + const commentBody = `${commentMagicPrefix}\nšŸš€ ${buildName} build has started... Please wait for the results! šŸ•`; + const { data: { id: commentId } } = await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + core.exportVariable('comment_id', commentId) + } diff --git a/.github/actions/update-native-preview-build-status/action.yml b/.github/actions/update-native-preview-build-status/action.yml new file mode 100644 index 000000000..66c42cdbd --- /dev/null +++ b/.github/actions/update-native-preview-build-status/action.yml @@ -0,0 +1,39 @@ +name: Update build status +description: 'Updates build status comment with the build results' + +inputs: + github-token: + description: 'Github token' + required: true + build-outcome: + description: 'Build outcome' + required: true + +runs: + using: 'composite' + steps: + - name: Update build status + uses: actions/github-script@v6 + with: + github-token: ${{ inputs.github-token }} + script: | + const commentId = '${{ env.comment_id }}'; + const buildOutcome = '${{ inputs.build-outcome }}'; + const buildStatus = buildOutcome == 'success' ? 'completed' : 'failed'; + const buildName = '${{ env.build-name }}'; + const workflowUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + let commentBody = `${{ env.comment-unique-magic-prefix }}\n${buildName} build ${buildStatus}!`; + + if (buildOutcome == 'success') { + commentBody += `\nYou can download the ${buildName} from the following link:\n${workflowUrl}#artifacts`; + } else { + commentBody += '\nPlease check the workflow logs for more details on the build failure.'; + } + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: commentId, + body: commentBody + }); \ No newline at end of file diff --git a/.github/workflows/android-build-manual.yml b/.github/workflows/android-build-manual.yml deleted file mode 100644 index 3aa325482..000000000 --- a/.github/workflows/android-build-manual.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: android build manual - -on: - workflow_dispatch: - inputs: - name: - description: 'Build manually' - default: 'World' - required: true - type: string - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - update: - name: EAS Android Preview Build - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check for EXPO_TOKEN - run: | - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" - exit 1 - fi - - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 20.x - cache: yarn - - - name: Setup EAS - uses: expo/expo-github-action@v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Install dependencies - uses: ./.github/actions/install-deps - - - name: Create preview - uses: expo/expo-github-action/preview@v8 - with: - working-directory: apps/expo - command: eas build --platform android --profile preview \ No newline at end of file diff --git a/.github/workflows/android-preview-build-local.yml b/.github/workflows/android-preview-build-local.yml deleted file mode 100644 index a528f8e8d..000000000 --- a/.github/workflows/android-preview-build-local.yml +++ /dev/null @@ -1,138 +0,0 @@ -name: android-preview-build-local -on: - push: - branches: ['**'] - pull_request: - branches: ['**'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - -jobs: - update: - name: EAS Android Build Local - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check for EXPO_TOKEN - run: | - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets." - exit 1 - fi - - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 20.x - cache: yarn - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - - name: Setup EAS - uses: expo/expo-github-action@v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Export secrets as environment variables - env: - JSON_SECRETS: '${{ toJSON(secrets) }}' - run: | - eval "$(jq -r 'to_entries | map("export \(.key)=\(.value|tostring)") | .[]' <<< "$JSON_SECRETS")" - - - name: Install dependencies - uses: ./.github/actions/install-deps - - - name: Prebuild - run: | - echo "Using Mapbox Token: $MAPBOX_DOWNLOADS_TOKEN" - export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} - yarn run prebuild:expo - - - name: Create preview - id: build - run: | - echo "Using Mapbox Token: $MAPBOX_DOWNLOADS_TOKEN" - export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} - eas build --platform android --profile preview --local - apk_path=$(find . -name '*.apk') - echo "APK Path: ${apk_path}" - echo "apk_path=${apk_path}" >> $GITHUB_ENV - working-directory: apps/expo - env: - DEBUG: 'true' - continue-on-error: true - - - name: Upload APK - uses: actions/upload-artifact@v3 - with: - name: android-apk - path: /home/runner/work/PackRat/PackRat/apps/expo/build-*.apk - - - name: Find or create comment - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 - id: find_or_create_comment - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commentIdentifier = ''; - const comments = await github.rest.issues.listComments({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }); - - const existingComment = comments.data.find(comment => comment.body.startsWith(commentIdentifier)); - - if (existingComment) { - core.setOutput('comment_id', existingComment.id); - } else { - const commentBody = `${commentIdentifier}\nšŸš€ Android APK build started... Please wait for the results! šŸ•`; - const { data: { id: commentId } } = await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: commentBody - }); - core.setOutput('comment_id', commentId); - } - - - name: Update PR comment - if: always() && github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commentIdentifier = ''; - const commentId = '${{ steps.find_or_create_comment.outputs.comment_id }}'; - const buildOutcome = '${{ steps.build.outcome }}'; - const buildStatus = buildOutcome == 'success' ? 'completed' : 'failed'; - const workflowUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - - let commentBody = `${commentIdentifier}\nAndroid APK build ${buildStatus}!`; - - if (buildOutcome == 'success') { - commentBody += `\nYou can download the APK file from the following link:\n${workflowUrl}#artifacts`; - } else { - commentBody += '\nPlease check the workflow logs for more details on the build failure.'; - } - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: commentId, - body: commentBody - }); diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index b21e90816..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: android-build-apk-gradlew - -on: - push: - branches: ['**'] - pull_request: - branches: ['**'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-android-gradlew: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Standard Environment - uses: ./.github/actions/setup-standard-environment - with: - expo-token: ${{ secrets.EXPO_TOKEN }} - - - name: Prebuild - run: | - echo "Using Mapbox Token: $MAPBOX_DOWNLOADS_TOKEN" - export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} - yarn run prebuild:expo - - - name: Build Android Release - run: | - cd apps/expo/android && ./gradlew assembleRelease - - - name: Upload Artifact - uses: actions/upload-artifact@v1 - - with: - name: app-release.apk - path: android/app/build/outputs/apk/release/ diff --git a/.github/workflows/eas-build-manual.yml b/.github/workflows/eas-build-manual.yml deleted file mode 100644 index 2e541c7b8..000000000 --- a/.github/workflows/eas-build-manual.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: EAS Build Manual - -on: - workflow_dispatch: - inputs: - platform: - description: 'Platform (android or ios)' - required: true - default: 'android' - type: choice - options: - - android - - ios - build-type: - description: 'Build type (preview or local)' - required: true - default: 'preview' - type: choice - options: - - preview - - local - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - -jobs: - update: - name: EAS ${{ github.event.inputs.platform }} ${{ github.event.inputs.build-type }} Build - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - name: Setup EAS - uses: ./.github/actions/setup-eas - with: - expo-token: ${{ secrets.EXPO_TOKEN }} - node-version: '20.x' - - - name: Create build - uses: expo/expo-github-action/preview@v8 - with: - working-directory: apps/expo - command: | - eas build --platform ${{ github.event.inputs.platform }} --profile preview \ - ${{ github.event.inputs.build-type == 'local' && '--local' || '' }} \ No newline at end of file diff --git a/.github/workflows/eas-cloud.yml b/.github/workflows/eas-cloud.yml new file mode 100644 index 000000000..c9a0e63fe --- /dev/null +++ b/.github/workflows/eas-cloud.yml @@ -0,0 +1,36 @@ +# Native production pipeline +# Builds on EAS Cloud with auto submission +name: EAS Build & Submit + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'packages/app/**' + - 'apps/expo/**' + - 'packages/ui/**' + - 'packages/shared-types/**' + - 'packages/config/**' + - 'packages/crosspath/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup EAS + uses: ./.github/actions/setup-eas + with: + expo-token: ${{ secrets.EXPO_TOKEN }} + + - name: Build on EAS + working-directory: ./apps/expo + run: eas build --platform all --profile production --non-interactive --no-wait --auto-submit \ No newline at end of file diff --git a/.github/workflows/expo.build.yml b/.github/workflows/eas-local.yml similarity index 55% rename from .github/workflows/expo.build.yml rename to .github/workflows/eas-local.yml index ea9210d73..fe2803618 100644 --- a/.github/workflows/expo.build.yml +++ b/.github/workflows/eas-local.yml @@ -1,10 +1,25 @@ name: EAS Local Build on: + workflow_dispatch: push: branches: [ "**" ] + paths: + - 'packages/app/**' + - 'apps/expo/**' + - 'packages/ui/**' + - 'packages/shared-types/**' + - 'packages/config/**' + - 'packages/crosspath/**' pull_request: branches: [ "**" ] + paths: + - 'packages/app/**' + - 'apps/expo/**' + - 'packages/ui/**' + - 'packages/shared-types/**' + - 'packages/config/**' + - 'packages/crosspath/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,10 +34,23 @@ env: # Define a shared environment variable for android and ios build. jobs: build-android: runs-on: ubuntu-latest + + # will be used in find_or_create_comment + # and update_native_build_preview action + env: + build-name: Android APK + comment-unique-magic-prefix: '' + steps: - name: Checkout code uses: actions/checkout@v2 + - name: Find or create comment for build status + if: github.event_name == 'pull_request' + uses: ./.github/actions/find-or-create-comment + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Standard Environment uses: ./.github/actions/setup-standard-environment with: @@ -35,6 +63,7 @@ jobs: distribution: 'adopt' - name: Run Local Build for Android + id: build working-directory: ./apps/expo run: | eas build --profile preview --platform android --local --non-interactive @@ -46,13 +75,32 @@ jobs: with: name: android-apk path: /home/runner/work/PackRat/PackRat/apps/expo/build-*.apk + + - name: Update build status + if: always() && github.event_name == 'pull_request' + uses: ./.github/actions/update-native-preview-build-status + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + build-outcome: ${{ steps.build.outcome }} + build-ios: runs-on: macos-latest + + env: + build-name: iOS IPA + comment-unique-magic-prefix: '' + steps: - name: Checkout code uses: actions/checkout@v2 + - name: Find or create comment for build status + if: github.event_name == 'pull_request' + uses: ./.github/actions/find-or-create-comment + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Standard Environment uses: ./.github/actions/setup-standard-environment with: @@ -64,6 +112,7 @@ jobs: # xcode-version: latest-stable - name: Run Local Build for iOS + id: build working-directory: ./apps/expo run: | export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} @@ -75,4 +124,12 @@ jobs: uses: actions/upload-artifact@v3 with: name: ios-ipa - path: /home/runner/work/PackRat/PackRat/apps/expo/build-*.ipa + path: /Users/runner/work/PackRat/PackRat/apps/expo/build-*.ipa + + - name: Update build status + if: always() && github.event_name == 'pull_request' + uses: ./.github/actions/update-native-preview-build-status + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + build-outcome: ${{ steps.build.outcome }} + diff --git a/.github/workflows/ios-build-local.yml b/.github/workflows/ios-build-local.yml deleted file mode 100644 index 5e6254dcc..000000000 --- a/.github/workflows/ios-build-local.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: iOS App Release Build - -on: - push: - branches: ['**'] - pull_request: - branches: ['**'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - -jobs: - build: - runs-on: macos-latest - - steps: - - name: Setup repo - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 18.x - cache: 'yarn' - - - name: Setup EAS - uses: expo/expo-github-action@v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Install dependencies - run: yarn install - - - name: Setup environment variables - run: | - echo "MAPBOX_DOWNLOADS_TOKEN=$MAPBOX_DOWNLOADS_TOKEN" >> $GITHUB_ENV - - - name: Prebuild - run: | - echo "Using Mapbox Token: $MAPBOX_DOWNLOADS_TOKEN" - export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} - yarn run prebuild:expo - - - name: Build iOS app - id: build - run: | - echo "Using Mapbox Token: $MAPBOX_DOWNLOADS_TOKEN" - export MAPBOX_DOWNLOADS_TOKEN=${{ secrets.MAPBOX_DOWNLOADS_TOKEN }} - eas build --platform ios --profile preview --local --non-interactive --output ${{ github.workspace }}/app-release.ipa - env: - DEBUG: 'true' - continue-on-error: true - - - name: Upload IPA artifact - uses: actions/upload-artifact@v3 - with: - name: app-release - path: ${{ github.workspace }}/app-release.ipa - - - name: Find or create comment - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 - id: find_or_create_comment - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commentIdentifier = ''; - const comments = await github.rest.issues.listComments({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }); - - const existingComment = comments.data.find(comment => comment.body.startsWith(commentIdentifier)); - - if (existingComment) { - core.setOutput('comment_id', existingComment.id); - } else { - const commentBody = `${commentIdentifier}\nšŸš€ iOS app build started... Please wait for the results! šŸ•`; - const { data: { id: commentId } } = await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: commentBody - }); - core.setOutput('comment_id', commentId); - } - - name: Update PR comment - if: always() && github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commentIdentifier = ''; - const commentId = '${{ steps.find_or_create_comment.outputs.comment_id }}'; - const buildOutcome = '${{ steps.build.outcome }}'; - const buildStatus = buildOutcome == 'success' ? 'completed' : 'failed'; - const workflowUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - - let commentBody = `${commentIdentifier}\niOS app build ${buildStatus}!`; - - if (buildOutcome == 'success') { - commentBody += `\nYou can download the IPA file from the following link:\n${workflowUrl}#artifacts`; - } else { - commentBody += '\nPlease check the workflow logs for more details on the build failure.'; - } - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: commentId, - body: commentBody - }); diff --git a/apps/tauri/src/routes/__root.tsx b/apps/tauri/src/routes/__root.tsx index 87537688b..cb8662bca 100644 --- a/apps/tauri/src/routes/__root.tsx +++ b/apps/tauri/src/routes/__root.tsx @@ -2,16 +2,20 @@ import React from 'react'; import { MainContentWeb } from '@packrat/ui'; import { createRootRoute, Link, Outlet } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; -import { Navbar } from 'app/components/navigation'; +import { NavbarTauri } from 'app/components/navigation'; import { Provider } from 'app/provider'; +import { View } from 'react-native'; +import { FullSideBar } from 'app/components/navigation/SideBar'; export const Route = createRootRoute({ component: () => ( - - - - + + + + + + ), diff --git a/apps/tauri/src/routes/index.tsx b/apps/tauri/src/routes/index.tsx index 97cc08a58..20942ba83 100644 --- a/apps/tauri/src/routes/index.tsx +++ b/apps/tauri/src/routes/index.tsx @@ -3,6 +3,7 @@ import Dashboard from 'app/screens/dashboard'; import LandingPage from 'app/components/landing_page'; import { useAuthUser } from 'app/auth/hooks'; import { createFileRoute } from '@tanstack/react-router'; +import { ScrollView } from 'react-native'; export const Route = createFileRoute('/')({ component: Home, @@ -11,5 +12,15 @@ export const Route = createFileRoute('/')({ export default function Home() { const user = useAuthUser(); - return <>{!user ? : }; + return ( + <> + {!user ? ( + + ) : ( + + + + )} + + ); } diff --git a/packages/app/components/ScoreContainer.tsx b/packages/app/components/ScoreContainer.tsx index e2c7c97b2..ce9ed5262 100644 --- a/packages/app/components/ScoreContainer.tsx +++ b/packages/app/components/ScoreContainer.tsx @@ -238,7 +238,7 @@ const loadStyles = (theme: any) => { paddingHorizontal: 25, marginVertical: 15, padding: 26, - marginTop:25, + marginTop: 25, // borderColor: currentTheme.colors.border, // borderWidth: 2, borderColor: currentTheme.colors.border, @@ -258,7 +258,6 @@ const loadStyles = (theme: any) => { justifyContent: 'center', alignItems: 'flex-start', width: '60%', - }, vStackXS: { justifyContent: 'center', @@ -280,7 +279,7 @@ const loadStyles = (theme: any) => { }, buttonText: { color: currentTheme.colors.white, - fontSize: '1rem', + fontSize: 16, fontWeight: '500', }, container: { diff --git a/packages/app/components/feedPreview/FeedPreviewCard.tsx b/packages/app/components/feedPreview/FeedPreviewCard.tsx index 49b976c71..f3d30a82f 100644 --- a/packages/app/components/feedPreview/FeedPreviewCard.tsx +++ b/packages/app/components/feedPreview/FeedPreviewCard.tsx @@ -4,7 +4,7 @@ import { RLink } from '@packrat/ui'; import { LayoutChangeEvent, View } from 'react-native'; import useCustomStyles from 'app/hooks/useCustomStyles'; import loadStyles from './feedpreview.style'; -import { AntDesign, MaterialIcons } from '@expo/vector-icons'; +import { AntDesign, Fontisto, MaterialIcons } from '@expo/vector-icons'; import useTheme from 'app/hooks/useTheme'; import { useItemWeightUnit } from 'app/hooks/items'; import { convertWeight } from 'app/utils/convertWeight'; @@ -17,15 +17,24 @@ export type FeedItem = any; interface FeedPreviewCardProps { linkStr: string; item: FeedItem; + feedType: string; } const RText: any = OriginalRText; -const FeedPreviewCard: React.FC = ({ linkStr, item }) => { +const FeedPreviewCard: React.FC = ({ + linkStr, + item, + feedType, +}) => { const { currentTheme } = useTheme(); const styles = useCustomStyles(loadStyles); const [weightUnit] = useItemWeightUnit(); - const formattedWeight = convertWeight(item.total_weight, 'g', weightUnit); + const formattedWeight = convertWeight( + item.total_weight ?? item.weight, + item.unit ?? 'g', + weightUnit, + ); const [cardWidth, setCardWidth] = useState(); const handleSetCardWidth = (event: LayoutChangeEvent) => { @@ -33,6 +42,93 @@ const FeedPreviewCard: React.FC = ({ linkStr, item }) => { 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 ( @@ -76,7 +172,7 @@ const FeedPreviewCard: React.FC = ({ linkStr, item }) => { > {formatNumber(formattedWeight)} {weightUnit} @@ -95,7 +191,7 @@ const FeedPreviewCard: React.FC = ({ linkStr, item }) => { /> {item.favorites_count} @@ -114,7 +210,7 @@ const FeedPreviewCard: React.FC = ({ linkStr, item }) => { /> {new Date(item.createdAt).toLocaleString('en-US', { month: 'short', @@ -128,7 +224,7 @@ const FeedPreviewCard: React.FC = ({ linkStr, item }) => { Ttl Score: {item.total_score} diff --git a/packages/app/components/feedPreview/index.tsx b/packages/app/components/feedPreview/index.tsx index d4bd8fdbf..b2649ca9c 100644 --- a/packages/app/components/feedPreview/index.tsx +++ b/packages/app/components/feedPreview/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Carousel from '../carousel'; import { useFeed } from 'app/hooks/feed'; import { default as FeedPreviewCard, type FeedItem } from './FeedPreviewCard'; +import Loader from 'app/components/Loader'; interface FeedPreviewScrollProps { itemWidth: number; @@ -14,16 +15,18 @@ const FeedPreviewScroll: React.FC = ({ feedType, id, }) => { - const { data: feedData } = useFeed({ feedType, id }); + const { data: feedData, isLoading } = useFeed({ feedType, id }); - return ( + return isLoading ? ( + + ) : ( {feedData ?.filter((item): item is FeedItem => item.type !== null) .map((item: FeedItem) => { const linkStr = `/${item.type}/${item.id}`; return linkStr ? ( - + ) : null; })} diff --git a/packages/app/components/navigation/Navbar/Navbar.tsx b/packages/app/components/navigation/Navbar/Navbar.tsx index 74f1812df..3a1d2741f 100644 --- a/packages/app/components/navigation/Navbar/Navbar.tsx +++ b/packages/app/components/navigation/Navbar/Navbar.tsx @@ -93,7 +93,7 @@ const loadStyles = (currentTheme, isScrolled, screenWidth) => { position: 'fixed' as 'fixed' | 'relative', top: 0, zIndex: 100, - width: Platform.OS === 'web' ? '100vw' : "100%", + width: Platform.OS === 'web' ? '100vw' : '100%', }, }), }, diff --git a/packages/app/components/navigation/Navbar/NavbarTauri.tsx b/packages/app/components/navigation/Navbar/NavbarTauri.tsx new file mode 100644 index 000000000..dec9d0800 --- /dev/null +++ b/packages/app/components/navigation/Navbar/NavbarTauri.tsx @@ -0,0 +1,161 @@ +import React, { useMemo } from 'react'; +import { View, Text, SafeAreaView, StyleSheet, Platform } from 'react-native'; +import { RButton, Container } from '@packrat/ui'; +import { useIsMobileView } from 'app/hooks/common'; +import { useNavigate } from 'app/hooks/navigation'; +import { NavigationList } from '../NavigationList'; +import { Drawer } from '../Drawer'; +import { useScrollTop } from 'app/hooks/common/useScrollTop'; +import { useScreenWidth } from 'app/hooks/common'; +import useTheme from 'app/hooks/useTheme'; +import { RImage } from '@packrat/ui'; +import FullSideBar from '../Sidebar/SideBar'; + +export const NavbarTauri = () => { + const { currentTheme } = useTheme(); + const scrollTop = useScrollTop(); + const { screenWidth } = useScreenWidth(); + const isScrolled = !!scrollTop; + const styles = useMemo(() => { + return StyleSheet.create(loadStyles(currentTheme, isScrolled, screenWidth)); + }, [isScrolled, currentTheme, screenWidth]); + const navigate = useNavigate(); + + return ( + + + + + { + navigate('/'); + }} + /> + { + navigate('/'); + }} + > + PackRat + + + + + + + ); +}; + +const NavbarStyles = { + floatingBg: '#0284c7', + floatingRadius: 25, + floatingBlur: 'blur(2px)', + transition: 'all 0.2s ease-in-out', + floatingSpacing: 4, +}; + +const loadStyles = (currentTheme, isScrolled, screenWidth) => { + const isWeb = Platform.OS === 'web'; + const isFloating = isWeb && isScrolled; + const backgroundColor = isFloating + ? NavbarStyles.floatingBg + : currentTheme.colors.background; + + return StyleSheet.create({ + drawerStyles: { + backgroundColor: currentTheme.colors.background, + }, + safeArea: { + backgroundColor, + width: '100%', + margin: 0, + transition: NavbarStyles.transition, + ...Platform.select({ + web: { + ...(isFloating + ? { + backdropFilter: NavbarStyles.floatingBlur, + marginTop: NavbarStyles.floatingSpacing, + padding: NavbarStyles.floatingSpacing, + borderRadius: NavbarStyles.floatingRadius, + } + : {}), + position: 'fixed' as 'fixed' | 'relative', + top: 0, + zIndex: 100, + width: Platform.OS === 'web' ? '100vw' : '100%', + }, + }), + }, + container: { + width: '100vw', + maxWidth: '100%', // Ensure container does not exceed the viewport width + flex: 1, // Ensure container can grow to fit content + backgroundColor, + borderRadius: NavbarStyles.floatingRadius, + flexDirection: 'row', // Keep flexDirection as row for alignment + justifyContent: 'space-between', + alignItems: 'center', + flexWrap: 'wrap', // Allow items to wrap + height: 60, // Ensure container takes full height of its container + padding: 16, + }, + header: { + flexDirection: 'row', // Keep flexDirection as row for initial alignment + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', // Ensure header takes full width of its container + flexWrap: 'wrap', // Allow header items to wrap + }, + logoContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + logo: { + marginRight: 10, + cursor: 'pointer', + }, + logoText: { + color: currentTheme.colors.text, + fontSize: 38, + fontWeight: '900', + cursor: 'pointer', + }, + menuBar: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-end', + paddingHorizontal: 16, + flex: 1, // Keep flexible but consider its behavior with wrapping, + flexWrap: 'wrap', // Allow items to wrap + }, + drawerTrigger: {}, + menuBarItemActive: { + // Apply styles for the active item + // ... + }, + menuBarItemTextActive: { + // Apply styles for the active item's text + // ... + }, + menuBarItemSelected: { + // Apply styles for the selected item + // ... + }, + menuBarItemTextSelected: { + // Apply styles for the selected item's text + // ... + }, + }); +}; diff --git a/packages/app/components/navigation/Navbar/index.ts b/packages/app/components/navigation/Navbar/index.ts index ec1cfd76d..00bf99a9a 100644 --- a/packages/app/components/navigation/Navbar/index.ts +++ b/packages/app/components/navigation/Navbar/index.ts @@ -1 +1,2 @@ export { Navbar } from './Navbar'; +export { NavbarTauri } from './NavbarTauri'; diff --git a/packages/app/components/navigation/Sidebar/ProfileNavigationList.tsx b/packages/app/components/navigation/Sidebar/ProfileNavigationList.tsx new file mode 100644 index 000000000..003dd5d8c --- /dev/null +++ b/packages/app/components/navigation/Sidebar/ProfileNavigationList.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { useNavigationList } from 'app/hooks/navigation'; +import { NavigationItem } from '../NavigationItem'; +import { useIsMobileView } from 'app/hooks/common'; +import { View } from 'tamagui'; +import useTheme from '../../../hooks/useTheme'; + +interface ProfileNavigationList { + itemStyle?: any; + onItemSelect?: (item: any) => void; +} + +export const ProfileNavigationList = ({ itemStyle, onItemSelect }) => { + const isMobileView = useIsMobileView(); + const { currentTheme } = useTheme(); + const { navigationItems } = useNavigationList(); + + return ( + <> + {navigationItems + ?.filter(({ href }) => href === '/profile' || href === '/logout') + .map(({ type, ...Item }, index) => { + const item = Item as any; + console.log(item); + + return ( + + {type === 'link' ? ( + + ) : ( + item.Component && + )} + + ); + })} + + ); +}; diff --git a/packages/app/components/navigation/Sidebar/SideBar.tsx b/packages/app/components/navigation/Sidebar/SideBar.tsx new file mode 100644 index 000000000..3336f10eb --- /dev/null +++ b/packages/app/components/navigation/Sidebar/SideBar.tsx @@ -0,0 +1,99 @@ +import { useState } from 'react'; +import { View, useMedia, styled } from 'tamagui'; +import { RIconButton } from '@packrat/ui'; +import useTheme from 'app/hooks/useTheme'; +import Ionicons from '@expo/vector-icons/Ionicons'; +import { SidebarNavigationList } from './SidebarNavigationList'; +import { ProfileNavigationList } from './ProfileNavigationList'; + +export function FullSideBar() { + const [openDrawer, setOpenDrawer] = useState(false); + const { sm } = useMedia(); + + return ( + + {!sm && } + {sm && } + + + ); +} + +FullSideBar.fileName = 'FullSideBar'; + +function Sidebar() { + const { currentTheme } = useTheme(); + + return ( + + {}} + /> + + ); +} + +function ProfileDrawer({ + open, + setOpen, + onItemSelect, +}: { + open: boolean; + setOpen: (open: boolean) => void; + onItemSelect: (item: any) => void; +}) { + const { currentTheme } = useTheme(); + + return ( + + } + onPress={() => setOpen(!open)} + /> + {open && ( + + + + )} + + ); +} + +export default FullSideBar; diff --git a/packages/app/components/navigation/Sidebar/SidebarNavigationList.tsx b/packages/app/components/navigation/Sidebar/SidebarNavigationList.tsx new file mode 100644 index 000000000..e2a396070 --- /dev/null +++ b/packages/app/components/navigation/Sidebar/SidebarNavigationList.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useNavigationList } from 'app/hooks/navigation'; +import { NavigationItem } from '../NavigationItem'; +import { useIsMobileView } from 'app/hooks/common'; +import { View } from 'tamagui'; +import useTheme from '../../../hooks/useTheme'; + +interface SidebarNavigationList { + itemStyle?: any; + onItemSelect?: (item: any) => void; +} + +export const SidebarNavigationList = ({ itemStyle, onItemSelect }) => { + const isMobileView = useIsMobileView(); + const { currentTheme } = useTheme(); + const { navigationItems } = useNavigationList(); + + return ( + <> + {navigationItems + ?.filter( + ({ href, type }) => + href !== '/profile' && href !== '/logout' && type !== 'divider', + ) + .map(({ type, ...Item }, index) => { + const item = Item as any; + return ( + + {type === 'link' ? ( + + ) : ( + item.Component && + )} + + ); + })} + + ); +}; diff --git a/packages/app/hooks/feed/index.ts b/packages/app/hooks/feed/index.ts index f588cb888..636e8cfaa 100644 --- a/packages/app/hooks/feed/index.ts +++ b/packages/app/hooks/feed/index.ts @@ -1,7 +1,8 @@ import { usePublicFeed } from './publicFeed'; import { useUserPacks } from './../packs'; import { useUserTrips } from '../singletrips'; -import { useSimilarPacks } from 'app/hooks/packs/useSimilarPacks'; +import { useSimilarPacks } from 'app/hooks/packs'; +import { useSimilarItems } from 'app/hooks/items'; export const useFeed = ({ queryString = 'Most Recent', @@ -25,6 +26,8 @@ export const useFeed = ({ return useUserTrips(ownerId || undefined); case 'similarPacks': return useSimilarPacks(id); + case 'similarItems': + return useSimilarItems(id); default: return { data: null, error: null, isLoading: true }; } diff --git a/packages/app/hooks/items/index.ts b/packages/app/hooks/items/index.ts index 2512212e1..1244bc362 100644 --- a/packages/app/hooks/items/index.ts +++ b/packages/app/hooks/items/index.ts @@ -5,3 +5,4 @@ export { useItemsUpdater } from './useItemsUpdater'; export { useItemWeightUnit } from './useItemWeightUnit'; export { useItemId } from './useItemId'; export { useItem } from './useItem'; +export { useSimilarItems } from './useSimilarItems'; diff --git a/packages/app/hooks/items/useSimilarItems.ts b/packages/app/hooks/items/useSimilarItems.ts new file mode 100644 index 000000000..2b8db4967 --- /dev/null +++ b/packages/app/hooks/items/useSimilarItems.ts @@ -0,0 +1,13 @@ +import { queryTrpc } from '../../trpc'; + +export const useSimilarItems = (id: string) => { + const { data, error, isLoading, refetch } = + queryTrpc.getSimilarItems.useQuery( + { id, limit: 10 }, + { + refetchOnWindowFocus: true, + }, + ); + + return { data, error, isLoading, refetch }; +}; diff --git a/packages/app/screens/items/item-details.tsx b/packages/app/screens/items/item-details.tsx index e16046de5..d49ebdd7f 100644 --- a/packages/app/screens/items/item-details.tsx +++ b/packages/app/screens/items/item-details.tsx @@ -10,6 +10,7 @@ import { usePagination } from 'app/hooks/common'; import { BaseModal, DropdownComponent, + RH3, RImage, RScrollView, RStack, @@ -18,16 +19,20 @@ import { } from '@packrat/ui'; import useResponsive from 'app/hooks/useResponsive'; import { CustomCard } from 'app/components/card'; +import LargeCard from 'app/components/card/LargeCard'; +import FeedPreview from 'app/components/feedPreview'; export default function ItemDetails() { const { limit, handleLimitChange, page, handlePageChange } = usePagination(); const [itemId] = useItemId(); const { data: item, isError } = useItem(itemId); const styles = useCustomStyles(loadStyles); + const { currentTheme } = useTheme(); + console.log({ item, itemId }); return ( - + {!isError && item && ( @@ -55,6 +60,26 @@ export default function ItemDetails() { } type="item" /> + + + Similar Items + + + )}