Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ API_URL=
VAR_NUMBER=
VAR_BOOL=
SECRET_KEY=
WEBSITE_URL=
TERMS_OF_SERVICE_URL=
16 changes: 10 additions & 6 deletions env.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const LOCAL_BUILD_SCRIPT_PATTERNS = [
'expo export',
];
const isLocalBuild = LOCAL_BUILD_SCRIPT_PATTERNS.some((pattern) =>
process.env.npm_lifecycle_script?.includes(pattern)
process.env.npm_lifecycle_script?.includes(pattern),
);

const EXPO_RUN_COMMANDS = ['expo start', 'expo run'];
Expand All @@ -44,13 +44,13 @@ const ENVIRONMENT_DEPENDANT_SCRIPTS = [
];

const scriptIsEnvironmentDependant = ENVIRONMENT_DEPENDANT_SCRIPTS.some(
(script) => process.env.npm_lifecycle_script?.includes(script)
(script) => process.env.npm_lifecycle_script?.includes(script),
);

// Check if the environment file has to be validated for the current running script and build method
const isBuilding = isEASBuild || isLocalBuild;
const isRunning = EXPO_RUN_COMMANDS.some((script) =>
process.env.npm_lifecycle_script?.includes(script)
process.env.npm_lifecycle_script?.includes(script),
);
const shouldValidateEnv =
(isBuilding && scriptIsEnvironmentDependant) || isRunning;
Expand Down Expand Up @@ -138,6 +138,8 @@ const clientEnvSchema = z.object({
API_URL: z.string(),
VAR_NUMBER: z.number(),
VAR_BOOL: z.boolean(),
TERMS_OF_SERVICE_URL: z.string(),
WEBSITE_URL: z.string(),
});

const buildTimeEnvSchema = z.object({
Expand All @@ -162,6 +164,8 @@ const _clientEnv = {
API_URL: parseString(process.env.API_URL),
VAR_NUMBER: parseNumber(process.env.VAR_NUMBER),
VAR_BOOL: parseBoolean(process.env.VAR_BOOL),
WEBSITE_URL: parseString(process.env.WEBSITE_URL),
TERMS_OF_SERVICE_URL: parseString(process.env.TERMS_OF_SERVICE_URL),
};

/**
Expand Down Expand Up @@ -212,19 +216,19 @@ if (shouldValidateEnv) {

if (isLocalBuild) {
messages.push(
`\n💡 Tip: If you recently updated the \x1b[1m\x1b[4m\x1b[31m${envFile}\x1b[0m file and the error still persists, try restarting the server with the -cc flag to clear the cache.`
`\n💡 Tip: If you recently updated the \x1b[1m\x1b[4m\x1b[31m${envFile}\x1b[0m file and the error still persists, try restarting the server with the -cc flag to clear the cache.`,
);
}

if (isEASBuild) {
messages.push(
`\n☁️ For \x1b[1m\x1b[32mEAS Build\x1b[0m deployments, ensure the secret\x1b[1m\x1b[4m\x1b[31m${environmentFiles[APP_ENV]} \x1b[0m is defined in Project Secrets and has the proper environment file attached.`
`\n☁️ For \x1b[1m\x1b[32mEAS Build\x1b[0m deployments, ensure the secret\x1b[1m\x1b[4m\x1b[31m${environmentFiles[APP_ENV]} \x1b[0m is defined in Project Secrets and has the proper environment file attached.`,
);
}

console.error(...messages);
throw new Error(
'Invalid environment variables, Check terminal for more details '
'Invalid environment variables, Check terminal for more details ',
);
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"react-native-screens": "~3.31.1",
"react-native-svg": "~15.2.0",
"react-native-web": "~0.19.12",
"react-native-webview": "13.8.6",
"react-query-kit": "^3.3.0",
"tailwind-variants": "^0.2.1",
"zod": "^3.23.8",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 33 additions & 35 deletions src/app/(app)/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Env } from '@env';
import { Link } from 'expo-router';
import { useColorScheme } from 'nativewind';
import React from 'react';

import { Item } from '@/components/settings/item';
import { ItemsContainer } from '@/components/settings/items-container';
import { LanguageItem } from '@/components/settings/language-item';
import { ThemeItem } from '@/components/settings/theme-item';
import { translate, useAuth } from '@/core';
import { colors, FocusAwareStatusBar, ScrollView, Text, View } from '@/ui';
import { Github, Rate, Share, Support, Website } from '@/ui/icons';
import { Website } from '@/ui/icons';

export default function Settings() {
const signOut = useAuth.use.signOut();
Expand All @@ -29,42 +31,38 @@ export default function Settings() {
<ThemeItem />
</ItemsContainer>

<ItemsContainer title="settings.about">
<Item text="settings.app_name" value={Env.NAME} />
<Item text="settings.version" value={Env.VERSION} />
</ItemsContainer>

<ItemsContainer title="settings.support_us">
<Item
text="settings.share"
icon={<Share color={iconColor} />}
onPress={() => {}}
/>
<Item
text="settings.rate"
icon={<Rate color={iconColor} />}
onPress={() => {}}
/>
<Item
text="settings.support"
icon={<Support color={iconColor} />}
onPress={() => {}}
/>
<ItemsContainer title="settings.links">
<Link
asChild
href={{
pathname: '/www',
params: {
url: Env.TERMS_OF_SERVICE_URL,
title: translate('settings.terms'),
},
}}
>
<Item text="settings.terms" />
</Link>
<Link
asChild
href={{
pathname: '/www',
params: {
url: Env.WEBSITE_URL,
title: translate('settings.website'),
},
}}
>
<Item
text="settings.website"
icon={<Website color={iconColor} />}
/>
</Link>
</ItemsContainer>

<ItemsContainer title="settings.links">
<Item text="settings.privacy" onPress={() => {}} />
<Item text="settings.terms" onPress={() => {}} />
<Item
text="settings.github"
icon={<Github color={iconColor} />}
onPress={() => {}}
/>
<Item
text="settings.website"
icon={<Website color={iconColor} />}
onPress={() => {}}
/>
<ItemsContainer title="settings.about">
<Item text="settings.version" value={Env.VERSION} />
</ItemsContainer>

<View className="my-8">
Expand Down
2 changes: 2 additions & 0 deletions src/app/(app)/style.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from 'react';

import { Buttons } from '@/components/buttons';
import { Colors } from '@/components/colors';
import { Inputs } from '@/components/inputs';
Expand Down
7 changes: 7 additions & 0 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export default function RootLayout() {
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
<Stack.Screen name="forgot-password" />
<Stack.Screen name="sign-in" options={{ headerShown: false }} />
<Stack.Screen
name="www"
options={{
presentation: 'modal',
title: '', // Title will be overridden by the screen itself
}}
/>
</Stack>
</Providers>
);
Expand Down
21 changes: 12 additions & 9 deletions src/app/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
import { useRouter } from 'expo-router';

import { Cover } from '@/components/cover';
import { translate } from '@/core';
import { useIsFirstTime } from '@/core/hooks';
import { Button, FocusAwareStatusBar, SafeAreaView, Text, View } from '@/ui';

export default function Onboarding() {
const [, setIsFirstTime] = useIsFirstTime();
const router = useRouter();

return (
<View className="flex h-full items-center justify-center">
<View className="flex h-full items-center justify-center">
<FocusAwareStatusBar />
<View className="w-full flex-1">
<Cover />
</View>
<View className="justify-end ">
<View className="justify-end">
<Text className="my-3 text-center text-5xl font-bold">
React Native Template
{translate('onboarding.title')}
</Text>
<Text className="mb-2 text-center text-lg text-gray-600">
The right way to build your mobile app
{translate('onboarding.subtitle')}
</Text>

<Text className="my-1 pt-6 text-left text-lg">
🚀 Production-ready{' '}
{translate('onboarding.features.production_ready')}
</Text>
<Text className="my-1 text-left text-lg">
🥷 Developer experience + Productivity
{translate('onboarding.features.developer_experience')}
</Text>
<Text className="my-1 text-left text-lg">
🧩 Minimal code and dependencies
{translate('onboarding.features.minimal_code')}
</Text>
<Text className="my-1 text-left text-lg">
💪 well maintained third-party libraries
{translate('onboarding.features.well_maintained_libraries')}
</Text>
</View>
<SafeAreaView className="mt-6">
<Button
label="Let's Get Started "
label={translate('onboarding.button_label')}
onPress={() => {
setIsFirstTime(false);
router.replace('/sign-in');
Expand Down
1 change: 1 addition & 0 deletions src/app/sign-in.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useRouter } from 'expo-router';
import React from 'react';
import { showMessage } from 'react-native-flash-message';

import { useLogin } from '@/api/auth/use-login';
Expand Down
41 changes: 41 additions & 0 deletions src/app/www.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import React, { useEffect } from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';

import { translate } from '@/core';
import { Text } from '@/ui';

export default function WWW() {
const router = useRouter();
const { url, title } = useLocalSearchParams();
const navigation = useNavigation();

useEffect(() => {
if (title) {
navigation.setOptions({
title,
});
}
}, [navigation, title]);

if (!url || typeof url !== 'string') {
return (
<View className="flex-1 items-center justify-center bg-white">
<Text className="text-lg text-red-500">
{translate('www.invalidUrl')}
</Text>
</View>
);
}

return (
<View className="flex-1 bg-white">
<WebView
source={{ uri: url }}
className="flex-1"
onError={() => router.back()}
/>
</View>
);
}
2 changes: 1 addition & 1 deletion src/components/settings/language-item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';

import { translate, useSelectedLanguage } from '@/core';
import type { Language } from '@/core/i18n/resources';
Expand Down
4 changes: 2 additions & 2 deletions src/core/i18n/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ describe('getLanguage', () => {

describe('translate', () => {
it('should return translated string', () => {
const key: TxKeyPath = 'onboarding.message';
const key: TxKeyPath = 'onboarding.title';
const options = { lang: 'en' };
const translatedString = translate(key, options);
expect(translatedString).toBe('Welcome to rootstrap app site');
expect(translatedString).toBe('React Native Template');
});
});
15 changes: 13 additions & 2 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@
"title": "Forgot your password?"
},
"onboarding": {
"message": "Welcome to rootstrap app site"
"button_label": "Let's Get Started",
"features": {
"developer_experience": "🥷 Developer experience + Productivity",
"minimal_code": "🧩 Minimal code and dependencies",
"production_ready": "🚀 Production-ready",
"well_maintained_libraries": "💪 Well maintained third-party libraries"
},
"subtitle": "The right way to build your mobile app",
"title": "React Native Template"
},
"settings": {
"about": "About",
Expand Down Expand Up @@ -45,5 +53,8 @@
"version": "Version",
"website": "Website"
},
"welcome": "Welcome to rootstrap app site"
"welcome": "Welcome to rootstrap app site",
"www": {
"invalidUrl": "Invalid Url"
}
}