diff --git a/Dockerfile b/Dockerfile index ed3cb27d..e279fff4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,12 +3,16 @@ ARG NODE_VERSION=22.16.0 FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS base +ARG APP_VERSION +ARG GIT_SHA + ENV SKIP_ENV_VALIDATION="true" ENV DOCKER_OUTPUT=1 ENV NEXT_TELEMETRY_DISABLED=1 ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 -ENV APP_VERSION=${APP_VERSION} -ENV GIT_SHA=${GIT_SHA} + +ENV NEXT_PUBLIC_APP_VERSION=${APP_VERSION} +ENV NEXT_PUBLIC_GIT_SHA=${GIT_SHA} RUN apk update && apk add --no-cache libc6-compat @@ -24,9 +28,6 @@ RUN pnpm build FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS release -ARG APP_VERSION -ARG GIT_SHA - ENV NODE_ENV=production ENV DOCKER_OUTPUT=1 @@ -43,8 +44,6 @@ COPY --from=base /app/prisma/migrations ./prisma/migrations # set this so it throws error where starting server ENV SKIP_ENV_VALIDATION="false" -ENV APP_VERSION=${APP_VERSION} -ENV GIT_SHA=${GIT_SHA} COPY ./start.sh ./start.sh diff --git a/src/components/Account/DebugInfo.tsx b/src/components/Account/DebugInfo.tsx index 751ed7ba..7c5ab844 100644 --- a/src/components/Account/DebugInfo.tsx +++ b/src/components/Account/DebugInfo.tsx @@ -35,7 +35,7 @@ export const DebugInfo: React.FC = ({ children }) => { } }; - if (env.NEXT_PUBLIC_VERSION) { + if (env.NEXT_PUBLIC_APP_VERSION) { void fetchLatestVersion(); } }, []); @@ -46,8 +46,8 @@ export const DebugInfo: React.FC = ({ children }) => { if (env.NEXT_PUBLIC_GIT_SHA) { debugInfo.push(`${t('account.debug_info_details.git')}: ${env.NEXT_PUBLIC_GIT_SHA}`); } - if (env.NEXT_PUBLIC_VERSION) { - debugInfo.push(`${t('account.debug_info_details.version')} ${env.NEXT_PUBLIC_VERSION}`); + if (env.NEXT_PUBLIC_APP_VERSION) { + debugInfo.push(`${t('account.debug_info_details.version')} ${env.NEXT_PUBLIC_APP_VERSION}`); } try { void navigator.clipboard.writeText(debugInfo.join('\n')); @@ -78,10 +78,12 @@ export const DebugInfo: React.FC = ({ children }) => { /> - {newVersion && env.NEXT_PUBLIC_VERSION && newVersion !== env.NEXT_PUBLIC_VERSION ? ( + {newVersion && + env.NEXT_PUBLIC_APP_VERSION && + newVersion !== env.NEXT_PUBLIC_APP_VERSION ? (

{t('account.debug_info_details.new_version_available')}: {newVersion}

diff --git a/src/components/Account/UpdateDetails.tsx b/src/components/Account/UpdateDetails.tsx index 3edf8a4f..27983ef6 100644 --- a/src/components/Account/UpdateDetails.tsx +++ b/src/components/Account/UpdateDetails.tsx @@ -1,13 +1,12 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Camera, Pencil, X } from 'lucide-react'; import Cropper, { type Area } from 'react-easy-crop'; -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { type TFunction, useTranslation } from 'next-i18next'; import { toast } from 'sonner'; import { z } from 'zod'; -import { env } from '~/env'; import { prepareImageForUpload, uploadImage, validateUploadSize } from '~/utils/imageUpload'; import { AppDrawer } from '../ui/drawer'; @@ -17,6 +16,7 @@ import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Slider } from '../ui/slider'; import { Button } from '../ui/button'; +import { useAppStore } from '~/store/appStore'; const createImage = async (url: string) => { const image = new Image(); @@ -91,6 +91,7 @@ export const UpdateName: React.FC<{ const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); + const maxUploadFileSizeMB = useAppStore((s) => s.maxUploadFileSizeMB); const { t } = useTranslation(); @@ -151,14 +152,14 @@ export const UpdateName: React.FC<{ let croppedFile = new File([croppedBlob], 'avatar.jpg', { type: 'image/jpeg' }); try { - croppedFile = await prepareImageForUpload(croppedFile); + croppedFile = await prepareImageForUpload(croppedFile, maxUploadFileSizeMB); } catch (error) { console.error('Compression failed:', error); toast.error(t('errors.image_compression_failed')); } - if (!validateUploadSize(croppedFile)) { - toast.error(t('errors.less_than', { size: env.NEXT_PUBLIC_UPLOAD_MAX_FILE_SIZE_MB })); + if (!validateUploadSize(croppedFile, maxUploadFileSizeMB)) { + toast.error(t('errors.less_than', { size: maxUploadFileSizeMB })); return; } @@ -183,7 +184,7 @@ export const UpdateName: React.FC<{ setZoom(1); setCroppedAreaPixels(null); })(); - }, [croppedAreaPixels, detailForm, imageSrc, onNameSubmit, t]); + }, [croppedAreaPixels, detailForm, imageSrc, maxUploadFileSizeMB, onNameSubmit, t]); const handleFileChange = useCallback( (event: React.ChangeEvent) => { diff --git a/src/components/AddExpense/CurrencyPicker.tsx b/src/components/AddExpense/CurrencyPicker.tsx index 1f7d3394..31fe3b69 100644 --- a/src/components/AddExpense/CurrencyPicker.tsx +++ b/src/components/AddExpense/CurrencyPicker.tsx @@ -1,31 +1,20 @@ import { memo, useCallback, useMemo } from 'react'; -import { - CURRENCIES, - type CurrencyCode, - FRANKFURTER_CURRENCIES, - parseCurrencyCode, -} from '~/lib/currency'; +import { CURRENCIES, type CurrencyCode, parseCurrencyCode } from '~/lib/currency'; import { useTranslationWithUtils } from '~/hooks/useTranslationWithUtils'; import { GeneralPicker } from '../GeneralPicker'; import { Button } from '../ui/button'; import { useCurrencyPreferenceStore } from '~/store/currencyPreferenceStore'; -const FRANKFURTER_FILTERED_CURRENCIES = Object.fromEntries( - Object.entries(CURRENCIES).filter(([code]) => FRANKFURTER_CURRENCIES.includes(code)), -); - function CurrencyPickerInner({ className, currentCurrency = 'USD', onCurrencyPick, - showOnlyFrankfurter = false, }: { className?: string; currentCurrency: CurrencyCode; onCurrencyPick: (currency: CurrencyCode) => void; - showOnlyFrankfurter?: boolean; }) { const { t, getCurrencyName } = useTranslationWithUtils(['currencies']); const { recentCurrencies, addToRecentCurrencies } = useCurrencyPreferenceStore(); @@ -70,15 +59,13 @@ function CurrencyPickerInner({ [recentCurrencies], ); const items = useMemo(() => { - const baseItems = showOnlyFrankfurter - ? Object.values(FRANKFURTER_FILTERED_CURRENCIES) - : Object.values(CURRENCIES); + const baseItems = Object.values(CURRENCIES); const uniqueItems = [ ...recentCurrencyObjects, ...baseItems.filter((c) => !recentCurrencies.includes(c.code as CurrencyCode)), ]; return uniqueItems; - }, [showOnlyFrankfurter, recentCurrencyObjects, recentCurrencies]); + }, [recentCurrencyObjects, recentCurrencies]); return ( { const { t } = useTranslation(); const [file, setFile] = useState(null); + const maxUploadFileSizeMB = useAppStore((s) => s.maxUploadFileSizeMB); const fileKey = useAddExpenseStore((s) => s.fileKey); const { setFileUploading, setFileKey } = useAddExpenseStore((s) => s.actions); @@ -28,14 +29,14 @@ export const UploadFile: React.FC = () => { try { try { - file = await prepareImageForUpload(file); + file = await prepareImageForUpload(file, maxUploadFileSizeMB); } catch (error) { console.error('Compression failed:', error); toast.error(t('errors.image_compression_failed')); } - if (!validateUploadSize(file)) { - toast.error(t('errors.less_than', { size: env.NEXT_PUBLIC_UPLOAD_MAX_FILE_SIZE_MB })); + if (!validateUploadSize(file, maxUploadFileSizeMB)) { + toast.error(t('errors.less_than', { size: maxUploadFileSizeMB })); return; } @@ -54,7 +55,7 @@ export const UploadFile: React.FC = () => { setFileUploading(false); } }, - [setFileUploading, setFileKey, t], + [setFileUploading, setFileKey, maxUploadFileSizeMB, t], ); return ( diff --git a/src/components/Friend/CurrencyConversion.tsx b/src/components/Friend/CurrencyConversion.tsx index 8e927591..ada7bb73 100644 --- a/src/components/Friend/CurrencyConversion.tsx +++ b/src/components/Friend/CurrencyConversion.tsx @@ -4,7 +4,6 @@ import { api } from '~/utils/api'; import { MAX_RATE_PRECISION, currencyConversion, getRatePrecision } from '~/utils/numbers'; import { toast } from 'sonner'; -import { env } from '~/env'; import { type CurrencyCode, isCurrencyCode } from '~/lib/currency'; import { useAddExpenseStore } from '~/store/addStore'; import { CurrencyPicker } from '../AddExpense/CurrencyPicker'; @@ -216,8 +215,6 @@ export const CurrencyConversion: React.FC<{ className="mx-auto" currentCurrency={targetCurrency} onCurrencyPick={onChangeTargetCurrency} - // Client env vars with pages router only work after next build :/ - showOnlyFrankfurter={env.NEXT_PUBLIC_FRANKFURTER_USED} /> )} diff --git a/src/components/group/AddMembers.tsx b/src/components/group/AddMembers.tsx index b6cf4b10..e70dc463 100644 --- a/src/components/group/AddMembers.tsx +++ b/src/components/group/AddMembers.tsx @@ -12,7 +12,6 @@ import { api } from '~/utils/api'; import { EntityAvatar } from '../ui/avatar'; import { Input } from '../ui/input'; -import { env } from '~/env'; const AddMembers: React.FC<{ enableSendingInvites: boolean; @@ -34,13 +33,10 @@ const AddMembers: React.FC<{ return null; } - const groupUserMap = group.groupUsers.reduce( - (acc, gu) => { - acc[gu.userId] = true; - return acc; - }, - {} as Record, - ); + const groupUserMap = group.groupUsers.reduce>((acc, gu) => { + acc[gu.userId] = true; + return acc; + }, {}); const filteredUsers = friendsQuery.data?.filter( (friend) => @@ -126,7 +122,7 @@ const AddMembers: React.FC<{ onChange={(e) => setInputValue(e.target.value)} />
- {enableSendingInvites && !env.NEXT_PUBLIC_IS_CLOUD_DEPLOYMENT ? ( + {enableSendingInvites ? (
{t('group_details.no_members.add_members_details.warning')}
diff --git a/src/env.ts b/src/env.ts index 668ab6e0..64ac7c8e 100644 --- a/src/env.ts +++ b/src/env.ts @@ -77,13 +77,10 @@ export const env = createEnv({ /** * Specify your client-side environment variables schema here. This way you can ensure the app * isn't built with invalid env vars. To expose them to the client, prefix them with - * `NEXT_PUBLIC_`. + * `NEXT_PUBLIC_`. Note that these are only evaluated at build time! */ client: { - NEXT_PUBLIC_FRANKFURTER_USED: z.boolean().default(false), - NEXT_PUBLIC_IS_CLOUD_DEPLOYMENT: z.boolean().default(false), - NEXT_PUBLIC_UPLOAD_MAX_FILE_SIZE_MB: z.coerce.number().int().positive().default(10), - NEXT_PUBLIC_VERSION: z.string().optional(), + NEXT_PUBLIC_APP_VERSION: z.string().optional(), NEXT_PUBLIC_GIT_SHA: z.string().optional(), }, @@ -143,16 +140,11 @@ export const env = createEnv({ OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET, OIDC_WELL_KNOWN_URL: process.env.OIDC_WELL_KNOWN_URL, OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: Boolean(process.env.OIDC_ALLOW_DANGEROUS_EMAIL_LINKING), - NEXT_PUBLIC_FRANKFURTER_USED: process.env.CURRENCY_RATE_PROVIDER === 'frankfurter', - NEXT_PUBLIC_IS_CLOUD_DEPLOYMENT: process.env.NEXTAUTH_URL?.includes('splitpro.app') ?? false, - NEXT_PUBLIC_UPLOAD_MAX_FILE_SIZE_MB: process.env.UPLOAD_MAX_FILE_SIZE_MB - ? Number(process.env.UPLOAD_MAX_FILE_SIZE_MB) - : 10, UPLOAD_MAX_FILE_SIZE_MB: process.env.UPLOAD_MAX_FILE_SIZE_MB ? Number(process.env.UPLOAD_MAX_FILE_SIZE_MB) : 10, - NEXT_PUBLIC_VERSION: process.env.APP_VERSION, - NEXT_PUBLIC_GIT_SHA: process.env.GIT_SHA, + NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION, + NEXT_PUBLIC_GIT_SHA: process.env.NEXT_PUBLIC_GIT_SHA, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/src/lib/currency.ts b/src/lib/currency.ts index d0807170..5c5ead36 100644 --- a/src/lib/currency.ts +++ b/src/lib/currency.ts @@ -840,38 +840,3 @@ export const isCurrencyCode = (value: string): value is CurrencyCode => value in export const parseCurrencyCode = (code: string): CurrencyCode => isCurrencyCode(code) ? code : 'USD'; - -// Check with https://api.frankfurter.dev/v1/currencies -export const FRANKFURTER_CURRENCIES = [ - 'AUD', - 'BGN', - 'BRL', - 'CAD', - 'CHF', - 'CNY', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'IDR', - 'ILS', - 'INR', - 'ISK', - 'JPY', - 'KRW', - 'MXN', - 'MYR', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'RON', - 'SEK', - 'SGD', - 'THB', - 'TRY', - 'USD', - 'ZAR', -]; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e4afbf1e..1ce68a33 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -24,11 +24,13 @@ import '~/styles/globals.css'; const poppins = Poppins({ weight: ['200', '300', '400', '500', '600', '700'], subsets: ['latin'] }); const toastOptions = { duration: 1500 }; -const MyApp: AppType<{ session: Session | null; baseUrl: string }> = ({ +const MyApp: AppType<{ session: Session | null; baseUrl: string; maxUploadFileSizeMB: number }> = ({ Component, - pageProps: { session, baseUrl, ...pageProps }, + pageProps: { session, baseUrl, maxUploadFileSizeMB, ...pageProps }, }) => { const { t, ready } = useTranslation(); + const { setMaxUploadFileSizeMB } = useAppStore((s) => s.actions); + setMaxUploadFileSizeMB(maxUploadFileSizeMB); if (!ready) { return ( @@ -181,6 +183,7 @@ const Auth: React.FC<{ Page: NextPageWithUser; pageProps: any }> = ({ Page, page export const getServerSideProps = () => ({ props: { baseUrl: env.NEXTAUTH_URL, + maxUploadFileSizeMB: env.UPLOAD_MAX_FILE_SIZE_MB, }, }); diff --git a/src/pages/account.tsx b/src/pages/account.tsx index a6005cb7..05609b1b 100644 --- a/src/pages/account.tsx +++ b/src/pages/account.tsx @@ -87,8 +87,6 @@ const AccountPage: NextPageWithUser<{ void router.push('/auth/signin', '/auth/signin', { locale: 'default' }); }, [router]); - const isCloud = env.NEXT_PUBLIC_IS_CLOUD_DEPLOYMENT; - return ( <> @@ -131,13 +129,6 @@ const AccountPage: NextPageWithUser<{ - {isCloud && ( - - - {t('account.follow_on_x')} - - )} - {t('account.star_on_github')} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index c75bd737..224afd9e 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -21,7 +21,6 @@ import Link from 'next/link'; import { useTranslation } from 'next-i18next'; import { BackgroundGradient } from '~/components/ui/background-gradient'; import { Button } from '~/components/ui/button'; -import { env } from '~/env'; import { LanguageSelector } from '~/components/LanguageSelector'; import { customServerSideTranslations } from '~/utils/i18n/server'; @@ -52,26 +51,8 @@ const FeatureCard = ({ export default function Home() { const { t } = useTranslation('home'); - const isCloud = env.NEXT_PUBLIC_IS_CLOUD_DEPLOYMENT; - return ( <> - {isCloud && ( - - {'production' === process.env.NODE_ENV && ( - <> -