├── src
├── app
│ ├── storybook
│ │ ├── index.tsx
│ │ └── _layout.tsx
│ ├── (logged)
│ │ ├── (tabs)
│ │ │ ├── books.tsx
│ │ │ ├── home.tsx
│ │ │ └── account.tsx
│ │ ├── books
│ │ │ └── [id].tsx
│ │ └── _layout.tsx
│ ├── (public)
│ │ ├── sign-in.tsx
│ │ ├── otp-verification.tsx
│ │ └── _layout.tsx
│ ├── +not-found.tsx
│ ├── index.tsx
│ └── _layout.tsx
├── assets
│ └── images
│ │ ├── icon.png
│ │ ├── logo-dark.png
│ │ ├── logo-light.png
│ │ ├── splash-icon-dark.png
│ │ ├── splash-icon-light.png
│ │ ├── android-icon-foreground.png
│ │ └── android-icon-monochrome.png
├── features
│ ├── app-onboarding
│ │ ├── mascot.png
│ │ ├── layout-login-image.jpg
│ │ ├── store.ts
│ │ ├── use-background-animated-style.tsx
│ │ └── use-mascot-animated-style.tsx
│ ├── devtools
│ │ └── devtools.tsx
│ ├── books
│ │ └── book-cover.tsx
│ └── auth
│ │ ├── auth-header.tsx
│ │ └── client.ts
├── locales
│ ├── index.ts
│ ├── en
│ │ ├── books.json
│ │ ├── layout.json
│ │ ├── home.json
│ │ ├── common.json
│ │ ├── account.json
│ │ ├── app-onboarding.json
│ │ ├── index.ts
│ │ └── auth.json
│ └── fr
│ │ ├── books.json
│ │ ├── layout.json
│ │ ├── common.json
│ │ ├── home.json
│ │ ├── account.json
│ │ ├── app-onboarding.json
│ │ ├── index.ts
│ │ └── auth.json
├── components
│ ├── keep-awake.tsx
│ ├── ui
│ │ ├── image.tsx
│ │ ├── full-loader.tsx
│ │ ├── sonner.tsx
│ │ ├── card.tsx
│ │ └── animated-step-indicator.tsx
│ ├── version.tsx
│ ├── icons
│ │ ├── svg-sources
│ │ │ ├── icon-panel.svg
│ │ │ ├── icon-plus.svg
│ │ │ ├── icon-check.svg
│ │ │ ├── icon-chevron-down.svg
│ │ │ ├── icon-chevron-left.svg
│ │ │ ├── icon-chevron-right.svg
│ │ │ ├── icon-chevron-up.svg
│ │ │ ├── icon-play.svg
│ │ │ ├── icon-arrow-down.svg
│ │ │ ├── icon-arrow-up.svg
│ │ │ ├── icon-credit-card.svg
│ │ │ ├── icon-more-vertical.svg
│ │ │ ├── icon-arrow-left.svg
│ │ │ ├── icon-arrow-right.svg
│ │ │ ├── icon-clock.svg
│ │ │ ├── icon-dashboard.svg
│ │ │ ├── icon-check-circle.svg
│ │ │ ├── icon-search.svg
│ │ │ ├── icon-x.svg
│ │ │ ├── icon-chevrons-left.svg
│ │ │ ├── icon-chevrons-right.svg
│ │ │ ├── icon-alert-circle.svg
│ │ │ ├── icon-chevrons-up-down.svg
│ │ │ ├── icon-house-fill.svg
│ │ │ ├── icon-user.svg
│ │ │ ├── icon-lock.svg
│ │ │ ├── icon-folder.svg
│ │ │ ├── icon-home.svg
│ │ │ ├── icon-mail.svg
│ │ │ ├── icon-calendar.svg
│ │ │ ├── icon-file.svg
│ │ │ ├── icon-repository.svg
│ │ │ ├── icon-x-circle.svg
│ │ │ ├── icon-house.svg
│ │ │ ├── icon-circle-user-round.svg
│ │ │ ├── icon-copy.svg
│ │ │ ├── icon-book-open.svg
│ │ │ ├── icon-hash.svg
│ │ │ ├── icon-trash-2.svg
│ │ │ ├── icon-devices.svg
│ │ │ ├── icon-log-out.svg
│ │ │ ├── icon-download.svg
│ │ │ ├── icon-log-in.svg
│ │ │ ├── icon-share.svg
│ │ │ ├── icon-upload.svg
│ │ │ ├── icon-unlock.svg
│ │ │ ├── icon-tag.svg
│ │ │ ├── icon-external-link.svg
│ │ │ ├── icon-languages.svg
│ │ │ ├── icon-power.svg
│ │ │ ├── icon-edit-3.svg
│ │ │ ├── icon-book-open-fill.svg
│ │ │ ├── icon-shopping-cart.svg
│ │ │ ├── icon-moon.svg
│ │ │ ├── icon-eye.svg
│ │ │ ├── icon-eye-closed.svg
│ │ │ ├── icon-house-duotone.svg
│ │ │ ├── icon-share-2.svg
│ │ │ ├── icon-book-open-duotone.svg
│ │ │ ├── icon-user-circle-fill.svg
│ │ │ ├── icon-sliders.svg
│ │ │ └── icon-key.svg
│ │ ├── icon.tsx
│ │ ├── docs.stories.tsx
│ │ ├── generated
│ │ │ ├── IconPanel.tsx
│ │ │ ├── IconCheck.tsx
│ │ │ ├── IconChevronUp.tsx
│ │ │ ├── IconPlay.tsx
│ │ │ ├── IconChevronDown.tsx
│ │ │ ├── IconChevronLeft.tsx
│ │ │ ├── IconChevronRight.tsx
│ │ │ ├── IconPlus.tsx
│ │ │ ├── IconArrowDown.tsx
│ │ │ ├── IconArrowUp.tsx
│ │ │ ├── IconCreditCard.tsx
│ │ │ ├── IconDashboard.tsx
│ │ │ ├── IconCheckCircle.tsx
│ │ │ ├── IconChevronsLeft.tsx
│ │ │ ├── IconChevronsRight.tsx
│ │ │ ├── IconArrowLeft.tsx
│ │ │ ├── IconArrowRight.tsx
│ │ │ ├── IconClock.tsx
│ │ │ ├── IconSearch.tsx
│ │ │ ├── IconX.tsx
│ │ │ ├── IconMoreVertical.tsx
│ │ │ ├── IconChevronsUpDown.tsx
│ │ │ ├── IconHouseFill.tsx
│ │ │ ├── IconFolder.tsx
│ │ │ ├── IconAlertCircle.tsx
│ │ │ ├── IconUser.tsx
│ │ │ ├── IconLock.tsx
│ │ │ ├── IconRepository.tsx
│ │ │ ├── IconHome.tsx
│ │ │ ├── IconMail.tsx
│ │ │ ├── IconHouse.tsx
│ │ │ ├── IconFile.tsx
│ │ │ ├── IconCircleUserRound.tsx
│ │ │ ├── IconTrash2.tsx
│ │ │ ├── IconDevices.tsx
│ │ │ ├── IconCopy.tsx
│ │ │ ├── IconBookOpen.tsx
│ │ │ ├── IconXCircle.tsx
│ │ │ ├── IconCalendar.tsx
│ │ │ ├── IconUnlock.tsx
│ │ │ ├── IconTag.tsx
│ │ │ ├── IconHash.tsx
│ │ │ ├── IconLogOut.tsx
│ │ │ ├── IconLanguages.tsx
│ │ │ ├── IconLogIn.tsx
│ │ │ ├── IconShare.tsx
│ │ │ ├── IconUpload.tsx
│ │ │ ├── IconDownload.tsx
│ │ │ └── IconPower.tsx
│ │ └── svgr.config.cjs
│ ├── haptic-tab.tsx
│ ├── theme-manager.tsx
│ └── form
│ │ └── docs.stories.tsx
├── lib
│ ├── ficus-ui
│ │ ├── foundations
│ │ │ ├── font-sizes.ts
│ │ │ ├── fonts.ts
│ │ │ ├── colors.ts
│ │ │ └── index.ts
│ │ ├── theme.ts
│ │ └── components
│ │ │ ├── divider.ts
│ │ │ ├── text.ts
│ │ │ ├── index.ts
│ │ │ ├── input.ts
│ │ │ ├── badge.ts
│ │ │ └── button.ts
│ ├── hey-api
│ │ ├── api.ts
│ │ └── config.ts
│ ├── tanstack-form
│ │ ├── components
│ │ │ ├── form-field-helper.tsx
│ │ │ ├── form.tsx
│ │ │ ├── form-field-label.tsx
│ │ │ ├── index.ts
│ │ │ ├── form-submit.tsx
│ │ │ ├── form-field.tsx
│ │ │ └── form-field-error.tsx
│ │ └── context.ts
│ └── i18n
│ │ ├── constants.ts
│ │ ├── config.ts
│ │ └── index.ts
├── constants
│ └── device.ts
├── layout
│ ├── view-safe-content.tsx
│ ├── splash-screen-manager.tsx
│ └── view-tab-content.tsx
├── hooks
│ ├── use-browser
│ │ ├── docs.stories.tsx
│ │ └── index.tsx
│ ├── use-share
│ │ ├── docs.stories.tsx
│ │ └── index.tsx
│ ├── use-themed-style.tsx
│ └── use-theme-mode.tsx
└── types
│ └── utilities.d.ts
├── .github
├── assets
│ ├── thumbnail.png
│ └── tech-logos.png
└── workflows
│ ├── eas-build.yml
│ ├── eas-update.yml
│ ├── eas-preview.yml
│ └── code-quality.yml
├── .vscode
├── settings.example.json
└── extensions.json
├── .prettierignore
├── .env.example
├── lefthook.yml
├── .rnstorybook
├── index.ts
├── main.ts
└── storybook.requires.ts
├── .prettierrc.cjs
├── run-jiti.js
├── eas.json
├── metro.config.cjs
├── tsconfig.json
└── .gitignore
/src/app/storybook/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from '../../../.rnstorybook';
2 |
--------------------------------------------------------------------------------
/.github/assets/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/.github/assets/thumbnail.png
--------------------------------------------------------------------------------
/src/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/icon.png
--------------------------------------------------------------------------------
/.github/assets/tech-logos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/.github/assets/tech-logos.png
--------------------------------------------------------------------------------
/src/assets/images/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/logo-dark.png
--------------------------------------------------------------------------------
/src/assets/images/logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/logo-light.png
--------------------------------------------------------------------------------
/src/assets/images/splash-icon-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/splash-icon-dark.png
--------------------------------------------------------------------------------
/src/features/app-onboarding/mascot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/features/app-onboarding/mascot.png
--------------------------------------------------------------------------------
/src/locales/index.ts:
--------------------------------------------------------------------------------
1 | import en from '@/locales/en';
2 | import fr from '@/locales/fr';
3 |
4 | export default { en, fr } as const;
5 |
--------------------------------------------------------------------------------
/src/assets/images/splash-icon-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/splash-icon-light.png
--------------------------------------------------------------------------------
/.vscode/settings.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.readonlyInclude": {
3 | "**/*.gen.ts": true,
4 | "**/generated/**/*": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/images/android-icon-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/android-icon-foreground.png
--------------------------------------------------------------------------------
/src/assets/images/android-icon-monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/assets/images/android-icon-monochrome.png
--------------------------------------------------------------------------------
/src/features/app-onboarding/layout-login-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BearStudio/start-ui-native/HEAD/src/features/app-onboarding/layout-login-image.jpg
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .expo
2 | .vscode
3 | node_modules
4 | package.json
5 | package-lock.json
6 | yarn.lock
7 | pnpm-lock.yaml
8 | *.md
9 | *.mdx
10 | .env*
11 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "expo.vscode-expo-tools",
4 | "dbaeumer.vscode-eslint",
5 | "esbenp.prettier-vscode",
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/(logged)/(tabs)/books.tsx:
--------------------------------------------------------------------------------
1 | import { ViewBooks } from '@/features/books/view-books';
2 |
3 | export default function Books() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/(logged)/(tabs)/home.tsx:
--------------------------------------------------------------------------------
1 | import { ViewHome } from '@/features/home/view-home';
2 |
3 | export default function HomeScreen() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/(public)/sign-in.tsx:
--------------------------------------------------------------------------------
1 | import { ViewSignIn } from '@/features/auth/view-sign-in';
2 |
3 | export default function SignIn() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/keep-awake.tsx:
--------------------------------------------------------------------------------
1 | import { useKeepAwake } from 'expo-keep-awake';
2 |
3 | export const KeepAwake = () => {
4 | useKeepAwake();
5 |
6 | return <>>;
7 | };
8 |
--------------------------------------------------------------------------------
/src/locales/en/books.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "title": "Title",
4 | "author": "Author",
5 | "genre": "Genre",
6 | "publisher": "Publisher"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/locales/fr/books.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "title": "Titre",
4 | "author": "Auteur",
5 | "genre": "Genre",
6 | "publisher": "Éditeur"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/(logged)/(tabs)/account.tsx:
--------------------------------------------------------------------------------
1 | import { ViewAccount } from '@/features/account/view-account';
2 |
3 | export default function Account() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/(public)/otp-verification.tsx:
--------------------------------------------------------------------------------
1 | import { ViewOtpVerification } from '@/features/auth/view-otp-verification';
2 |
3 | export default function () {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/ui/image.tsx:
--------------------------------------------------------------------------------
1 | import { Image as ExpoImage, ImageProps } from 'expo-image';
2 |
3 | export const Image = (props: ImageProps) => {
4 | return ;
5 | };
6 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | EXPO_PUBLIC_BASE_URL=https://demo.start-ui.com
2 |
3 | # OPTIONAL TO OVERRIDE
4 | # EXPO_PUBLIC_AUTH_URL=https://demo.start-ui.com/api/auth
5 | # EXPO_PUBLIC_OPENAPI_URL=https://demo.start-ui.com/api/openapi/app/schema
--------------------------------------------------------------------------------
/src/lib/ficus-ui/foundations/font-sizes.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | '3xs': 8,
3 | '2xs': 10,
4 | xs: 12,
5 | sm: 14,
6 | md: 16,
7 | lg: 18,
8 | xl: 20,
9 | '2xl': 24,
10 | '3xl': 30,
11 | '4xl': 36,
12 | '5xl': 48,
13 | '6xl': 60,
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/ui/full-loader.tsx:
--------------------------------------------------------------------------------
1 | import { ActivityIndicator } from 'react-native';
2 | import { Center } from 'react-native-ficus-ui';
3 |
4 | export const FullLoader = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/app/(logged)/books/[id].tsx:
--------------------------------------------------------------------------------
1 | import { useLocalSearchParams } from 'expo-router';
2 |
3 | import { ViewBook } from '@/features/books/view-book';
4 |
5 | export default function Book() {
6 | const { id } = useLocalSearchParams<{ id: string }>();
7 |
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/foundations/fonts.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | fontFamily: {},
3 | fontWeights: {
4 | hairline: 100,
5 | thin: 200,
6 | light: 300,
7 | normal: 400,
8 | medium: 500,
9 | semibold: 600,
10 | bold: 700,
11 | extrabold: 800,
12 | black: 900,
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/constants/device.ts:
--------------------------------------------------------------------------------
1 | import * as Device from 'expo-device';
2 |
3 | export const isApple = Device.brand === 'Apple';
4 |
5 | export const isAndroid = Device.osName === 'Android';
6 |
7 | export const isTablet = Device.deviceType === Device.DeviceType.TABLET;
8 |
9 | export const isPhone = Device.deviceType === Device.DeviceType.PHONE;
10 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | commands:
3 | check:
4 | glob: '*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}'
5 | run: pnpm prettier --write {staged_files}
6 | stage_fixed: true
7 |
8 | pre-push:
9 | commands:
10 | check:
11 | glob: '*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}'
12 | run: pnpm lint
13 |
--------------------------------------------------------------------------------
/.rnstorybook/index.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 |
3 | import { view } from './storybook.requires';
4 |
5 | const StorybookUIRoot = view.getStorybookUI({
6 | storage: {
7 | getItem: AsyncStorage.getItem,
8 | setItem: AsyncStorage.setItem,
9 | },
10 | });
11 |
12 | export default StorybookUIRoot;
13 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/theme.ts:
--------------------------------------------------------------------------------
1 | import appConfig from 'app.config';
2 |
3 | import components from '@/lib/ficus-ui/components';
4 | import foundations from '@/lib/ficus-ui/foundations';
5 |
6 | export const STORAGE_KEY_THEME = `${appConfig.scheme}-theme`;
7 |
8 | export default {
9 | name: STORAGE_KEY_THEME,
10 | ...foundations,
11 | components,
12 | };
13 |
--------------------------------------------------------------------------------
/src/locales/en/layout.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabs": {
3 | "home": {
4 | "title": "Home"
5 | },
6 | "books": {
7 | "title": "Books"
8 | },
9 | "account": {
10 | "title": "Account"
11 | }
12 | },
13 | "notFound": {
14 | "title": "This page doesn't exists 🤔",
15 | "backInSafety": "Back in safe place"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/locales/fr/layout.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabs": {
3 | "home": {
4 | "title": "Accueil"
5 | },
6 | "books": {
7 | "title": "Livres"
8 | },
9 | "account": {
10 | "title": "Compte"
11 | }
12 | },
13 | "notFound": {
14 | "title": "Cette page n'existe pas 🤔",
15 | "backInSafety": "Retourner en lieu sûr"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/lib/hey-api/api.ts:
--------------------------------------------------------------------------------
1 | import { client } from '@/lib/hey-api/generated/client.gen';
2 |
3 | import { authClient } from '@/features/auth/client';
4 |
5 | export * as api from './generated/@tanstack/react-query.gen';
6 |
7 | client.interceptors.request.use((request) => {
8 | request.headers.append('Cookie', authClient.getCookie());
9 |
10 | return request;
11 | });
12 |
--------------------------------------------------------------------------------
/src/locales/en/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": {
3 | "title": "Welcome to Start UI [native]",
4 | "description": "An opinionated native starter created & maintained by the BearStudio Team and other contributors. It represents our team's up-to-date stack that we use when creating native apps for our clients.",
5 | "openIssue": "Open Issue",
6 | "share": "Share"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form-field-helper.tsx:
--------------------------------------------------------------------------------
1 | import { Text, TextProps } from 'react-native-ficus-ui';
2 |
3 | export const FormFieldHelper = (props: TextProps) => {
4 | return (
5 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/divider.ts:
--------------------------------------------------------------------------------
1 | import { defineStyleConfig, Dict, DividerProps } from 'react-native-ficus-ui';
2 |
3 | export default defineStyleConfig<
4 | DividerProps,
5 | Dict,
6 | Dict
7 | >({
8 | baseStyle: { color: 'neutral.200', _dark: { color: 'neutral.800' } },
9 | defaultProps: {},
10 | variants: {},
11 | sizes: {},
12 | });
13 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form.tsx:
--------------------------------------------------------------------------------
1 | import { AnyFormApi } from '@tanstack/react-form';
2 |
3 | import { formContext as FormContext } from '@/lib/tanstack-form/context';
4 |
5 | export const Form = (
6 | props: React.PropsWithChildren<{
7 | form: AnyFormApi;
8 | className?: string;
9 | }>
10 | ) => {
11 | return {props.children};
12 | };
13 |
--------------------------------------------------------------------------------
/.rnstorybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-native';
2 |
3 | const main: StorybookConfig = {
4 | stories: [
5 | './stories/**/*.stories.?(ts|tsx|js|jsx)',
6 | '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
7 | ],
8 | addons: [
9 | '@storybook/addon-ondevice-controls',
10 | '@storybook/addon-ondevice-actions',
11 | ],
12 | };
13 |
14 | export default main;
15 |
--------------------------------------------------------------------------------
/src/components/version.tsx:
--------------------------------------------------------------------------------
1 | import { nativeApplicationVersion, nativeBuildVersion } from 'expo-application';
2 | import { Text, TextProps } from 'react-native-ficus-ui';
3 |
4 | export const Version = (props: TextProps) => {
5 | return (
6 |
7 | Version {nativeApplicationVersion} • {nativeBuildVersion}
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "languages": {
3 | "label": "Language",
4 | "values": {
5 | "en": "English",
6 | "fr": "French",
7 | "ar": "Arabic",
8 | "sw": "Swahili"
9 | }
10 | },
11 | "themes": {
12 | "label": "Theme",
13 | "values": {
14 | "system": "System (auto)",
15 | "light": "Light",
16 | "dark": "Dark"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/locales/fr/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "languages": {
3 | "label": "Langue",
4 | "values": {
5 | "en": "Anglais",
6 | "fr": "Français",
7 | "ar": "Arabe",
8 | "sw": "Swahili"
9 | }
10 | },
11 | "themes": {
12 | "values": {
13 | "light": "Clair",
14 | "dark": "Sombre",
15 | "system": "Système (auto)"
16 | },
17 | "label": "Thème"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/locales/fr/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": {
3 | "title": "Bienvenue sur Start UI [native]",
4 | "description": "Un starter native \"opinionné\", conçu et maintenu par l’équipe BearStudio et d’autres contributeurs. Il reflète la stack actuelle que notre équipe privilégie pour développer des applications natives pour nos clients.",
5 | "openIssue": "Ouvrir un ticket",
6 | "share": "Partager"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/storybook/_layout.tsx:
--------------------------------------------------------------------------------
1 | import Constants from 'expo-constants';
2 | import { Stack } from 'expo-router';
3 |
4 | export default function StorybookLayout() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/layout/view-safe-content.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps } from 'react';
2 | import { ficus } from 'react-native-ficus-ui';
3 | import { SafeAreaView } from 'react-native-safe-area-context';
4 |
5 | const FicusSafeAreaView = ficus(SafeAreaView);
6 |
7 | export const ViewSafeContent = (
8 | props: ComponentProps
9 | ) => {
10 | return ;
11 | };
12 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/text.ts:
--------------------------------------------------------------------------------
1 | import { defineStyleConfig, Dict, TextProps } from 'react-native-ficus-ui';
2 |
3 | export default defineStyleConfig, Dict>({
4 | baseStyle: { color: 'neutral.900', _dark: { color: 'neutral.100' } },
5 | variants: {
6 | muted: {
7 | color: 'neutral.600',
8 | _dark: { color: 'neutral.400' },
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/foundations/colors.ts:
--------------------------------------------------------------------------------
1 | import tailwindColors from './tailwind-colors';
2 |
3 | export default {
4 | // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
5 | brand: tailwindColors.neutral,
6 | neutral: tailwindColors.neutral,
7 | positive: tailwindColors.green,
8 | negative: tailwindColors.red,
9 | warning: tailwindColors.amber,
10 | info: tailwindColors.sky,
11 | };
12 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-panel.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/hooks/use-browser/docs.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'react-native-ficus-ui';
2 |
3 | import { useBrowser } from '@/hooks/use-browser';
4 |
5 | export default {
6 | title: 'Hooks/useBrowser',
7 | };
8 |
9 | export const Default = () => {
10 | const browser = useBrowser();
11 | return (
12 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form-field-label.tsx:
--------------------------------------------------------------------------------
1 | import { Text, TextProps } from 'react-native-ficus-ui';
2 |
3 | type FormFieldLabelProps = TextProps;
4 |
5 | export const FormFieldLabel = (props: FormFieldLabelProps) => {
6 | return (
7 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/index.ts:
--------------------------------------------------------------------------------
1 | import Badge from './badge';
2 | import Button from './button';
3 | import Divider from './divider';
4 | import Input from './input';
5 | import { pinInputFieldTheme, pinInputTheme } from './pin-input';
6 | import Text from './text';
7 |
8 | export default {
9 | Badge,
10 | Button,
11 | Divider,
12 | Input,
13 | Text,
14 | PinInput: pinInputTheme,
15 | PinInputField: pinInputFieldTheme,
16 | };
17 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | // Prettier configuration. Please avoid changing the current configuration.
2 | // But if you do so, please run the `npm run pretty` command.
3 | /** @type {import("prettier").Options} */
4 | const config = {
5 | endOfLine: 'lf',
6 | semi: true,
7 | singleQuote: true,
8 | tabWidth: 2,
9 | trailingComma: 'es5',
10 | arrowParens: 'always',
11 | };
12 |
13 | // eslint-disable-next-line no-undef
14 | module.exports = config;
15 |
--------------------------------------------------------------------------------
/run-jiti.js:
--------------------------------------------------------------------------------
1 | import { createJiti } from 'jiti';
2 | import { fileURLToPath, URL } from 'node:url';
3 |
4 | const moduleFileUrl = import.meta.url;
5 |
6 | // eslint-disable-next-line no-undef
7 | const pathToFile = process.argv[2];
8 |
9 | // Allows aliases
10 | const jiti = createJiti(fileURLToPath(moduleFileUrl), {
11 | alias: {
12 | '@': fileURLToPath(new URL('./app', moduleFileUrl)),
13 | },
14 | });
15 |
16 | await jiti.import(pathToFile);
17 |
--------------------------------------------------------------------------------
/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps } from 'react';
2 | import { useColorScheme } from 'react-native';
3 | import { Toaster } from 'sonner-native';
4 |
5 | export const Sonner = ({ ...props }: ComponentProps) => {
6 | const scheme = useColorScheme();
7 | return (
8 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/foundations/index.ts:
--------------------------------------------------------------------------------
1 | import { Dict, Theme } from 'react-native-ficus-ui';
2 |
3 | import colors from '@/lib/ficus-ui/foundations/colors';
4 | import fontSizes from '@/lib/ficus-ui/foundations/font-sizes';
5 | import fonts from '@/lib/ficus-ui/foundations/fonts';
6 |
7 | export default {
8 | colors,
9 | fonts,
10 | fontSizes,
11 | } satisfies {
12 | colors: Dict;
13 | fonts: Theme['fonts'];
14 | fontSizes: Theme['fontSizes'];
15 | };
16 |
--------------------------------------------------------------------------------
/src/locales/en/account.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": {
3 | "signOut": "Sign out",
4 | "name": "Name",
5 | "email": "Email",
6 | "updateName": {
7 | "title": "Update name",
8 | "cancel": "Cancel",
9 | "save": "Save",
10 | "error": "An error occurred during name update"
11 | }
12 | },
13 | "displayPreferences": {
14 | "title": "Display preferences",
15 | "theme": "Theme",
16 | "language": "Language"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/locales/fr/account.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": {
3 | "signOut": "Déconnexion",
4 | "name": "Nom",
5 | "email": "Email",
6 | "updateName": {
7 | "title": "Modifier le nom",
8 | "cancel": "Annuler",
9 | "save": "Sauvegarder",
10 | "error": "Une erreur est survenue lors de la modification du nom"
11 | }
12 | },
13 | "displayPreferences": {
14 | "title": "Préférences d'affichage",
15 | "theme": "Thème",
16 | "language": "Langue"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/locales/en/app-onboarding.json:
--------------------------------------------------------------------------------
1 | {
2 | "continue": "Continue",
3 | "end": "Let's start",
4 | "welcome": {
5 | "title": "Welcome on"
6 | },
7 | "features": {
8 | "titleOne": "Opinionated",
9 | "titleTwo": "UI Starter",
10 | "authentication": "Authentication",
11 | "account": "Account",
12 | "darkMode": "Dark mode",
13 | "translations": "Translations",
14 | "storybook": "Storybook",
15 | "devtools": "Devtools",
16 | "more": "And more..."
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/hooks/use-share/docs.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'react-native-ficus-ui';
2 |
3 | import { useShare } from '@/hooks/use-share';
4 |
5 | export default {
6 | title: 'Hooks/useShare',
7 | };
8 |
9 | export const Default = () => {
10 | const share = useShare();
11 | return (
12 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/src/locales/fr/app-onboarding.json:
--------------------------------------------------------------------------------
1 | {
2 | "continue": "Continuer",
3 | "end": "Commencer",
4 | "welcome": {
5 | "title": "Bienvenue sur"
6 | },
7 | "features": {
8 | "titleOne": "\"Opinionated\"",
9 | "titleTwo": "UI Starter",
10 | "authentication": "Authentification",
11 | "account": "Compte",
12 | "darkMode": "Mode sombre",
13 | "translations": "Traductions",
14 | "storybook": "Storybook",
15 | "devtools": "Devtools",
16 | "more": "Et plus..."
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/hey-api/config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '@hey-api/openapi-ts';
2 |
3 | const openapiUrl: string =
4 | process.env.EXPO_PUBLIC_OPENAPI_URL ??
5 | `${process.env.EXPO_PUBLIC_BASE_URL}/api/openapi/app/schema`;
6 |
7 | export default defineConfig({
8 | input: { path: openapiUrl },
9 | output: { path: 'src/lib/hey-api/generated' },
10 | plugins: [
11 | {
12 | name: '@tanstack/react-query',
13 | },
14 | {
15 | name: '@hey-api/typescript',
16 | },
17 | ],
18 | });
19 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-plus.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevron-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevron-left.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevron-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevron-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/icon.tsx:
--------------------------------------------------------------------------------
1 | import { LucideIcon } from 'lucide-react-native';
2 | import { ficus, StyleProps } from 'react-native-ficus-ui';
3 |
4 | export const Icon = ({
5 | icon,
6 | size,
7 | ...props
8 | }: StyleProps & {
9 | icon: LucideIcon | ReturnType;
10 | color?: string;
11 | size?: number;
12 | }) => {
13 | const FicusLucideIcon = ficus(icon);
14 | return (
15 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-play.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/eas.json:
--------------------------------------------------------------------------------
1 | {
2 | "cli": {
3 | "version": ">= 16.20.0",
4 | "appVersionSource": "remote"
5 | },
6 | "build": {
7 | "development": {
8 | "developmentClient": true,
9 | "distribution": "internal",
10 | "channel": "development"
11 | },
12 | "preview": {
13 | "distribution": "internal",
14 | "channel": "preview"
15 | },
16 | "production": {
17 | "autoIncrement": true,
18 | "channel": "production"
19 | }
20 | },
21 | "submit": {
22 | "production": {}
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/metro.config.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-require-imports */
3 | const path = require('path');
4 | const { getDefaultConfig } = require('expo/metro-config');
5 |
6 | const withStorybook = require('@storybook/react-native/metro/withStorybook');
7 |
8 | /** @type {import('expo/metro-config').MetroConfig} */
9 | const config = getDefaultConfig(__dirname);
10 |
11 | module.exports = withStorybook(config, {
12 | enabled: true,
13 | configPath: path.resolve(__dirname, './.rnstorybook'),
14 | });
15 |
--------------------------------------------------------------------------------
/src/hooks/use-themed-style.tsx:
--------------------------------------------------------------------------------
1 | import { useColorModeValue } from 'react-native-ficus-ui';
2 |
3 | import theme from '@/lib/ficus-ui/theme';
4 |
5 | export const useThemedStyle = () => {
6 | return useColorModeValue(
7 | {
8 | backgroundColor: 'white',
9 | color: theme.colors.neutral[950],
10 | sceneBackgroundColor: theme.colors.neutral[50],
11 | },
12 | {
13 | backgroundColor: theme.colors.neutral[950],
14 | color: 'white',
15 | sceneBackgroundColor: theme.colors.neutral[900],
16 | }
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/src/components/haptic-tab.tsx:
--------------------------------------------------------------------------------
1 | import { PlatformPressable } from '@react-navigation/elements';
2 | import * as Haptics from 'expo-haptics';
3 | import { ComponentProps } from 'react';
4 |
5 | export const HapticTab = (props: ComponentProps) => {
6 | return (
7 | {
10 | if (process.env.EXPO_OS === 'ios') {
11 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
12 | }
13 | props.onPressIn?.(ev);
14 | }}
15 | />
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-arrow-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-arrow-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/index.ts:
--------------------------------------------------------------------------------
1 | import { Form as _Form } from './form';
2 | import { FormField as _FormField } from './form-field';
3 | import { FormFieldError as _FormFieldError } from './form-field-error';
4 | import { FormFieldHelper as _FormFieldHelper } from './form-field-helper';
5 | import { FormFieldLabel as _FormFieldLabel } from './form-field-label';
6 |
7 | export const Form = _Form;
8 | export const FormField = _FormField;
9 | export const FormFieldError = _FormFieldError;
10 | export const FormFieldHelper = _FormFieldHelper;
11 | export const FormFieldLabel = _FormFieldLabel;
12 |
--------------------------------------------------------------------------------
/src/features/app-onboarding/store.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import { create } from 'zustand';
3 | import { createJSONStorage, persist } from 'zustand/middleware';
4 |
5 | interface OnboardingStore {
6 | done: boolean;
7 | setDone: () => void;
8 | }
9 |
10 | export const useOnboardingStore = create()(
11 | persist(
12 | (set) => ({
13 | done: false,
14 | setDone: () => set({ done: true }),
15 | }),
16 | {
17 | name: 'onboarding',
18 | storage: createJSONStorage(() => AsyncStorage),
19 | }
20 | )
21 | );
22 |
--------------------------------------------------------------------------------
/src/features/devtools/devtools.tsx:
--------------------------------------------------------------------------------
1 | import { useAsyncStorageDevTools } from '@dev-plugins/async-storage';
2 | import { useReactNavigationDevTools } from '@dev-plugins/react-navigation';
3 | import { useReactQueryDevTools } from '@dev-plugins/react-query';
4 | import { useNavigationContainerRef } from 'expo-router';
5 |
6 | import { queryClient } from '@/app/_layout';
7 |
8 | export const DevTools = () => {
9 | useReactQueryDevTools(queryClient);
10 |
11 | const navigationRef = useNavigationContainerRef();
12 | useReactNavigationDevTools(navigationRef);
13 |
14 | useAsyncStorageDevTools();
15 |
16 | return <>>;
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-credit-card.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/locales/en/index.ts:
--------------------------------------------------------------------------------
1 | import 'dayjs/locale/en.js';
2 |
3 | import account from './account.json' with { type: 'json' };
4 | import appOnboarding from './app-onboarding.json' with { type: 'json' };
5 | import auth from './auth.json' with { type: 'json' };
6 | import books from './books.json' with { type: 'json' };
7 | import common from './common.json' with { type: 'json' };
8 | import home from './home.json' with { type: 'json' };
9 | import layout from './layout.json' with { type: 'json' };
10 |
11 | export default {
12 | account,
13 | books,
14 | auth,
15 | common,
16 | home,
17 | layout,
18 | appOnboarding,
19 | } as const;
20 |
--------------------------------------------------------------------------------
/src/locales/fr/index.ts:
--------------------------------------------------------------------------------
1 | import 'dayjs/locale/fr.js';
2 |
3 | import account from './account.json' with { type: 'json' };
4 | import appOnboarding from './app-onboarding.json' with { type: 'json' };
5 | import auth from './auth.json' with { type: 'json' };
6 | import books from './books.json' with { type: 'json' };
7 | import common from './common.json' with { type: 'json' };
8 | import home from './home.json' with { type: 'json' };
9 | import layout from './layout.json' with { type: 'json' };
10 |
11 | export default {
12 | account,
13 | auth,
14 | books,
15 | common,
16 | home,
17 | layout,
18 | appOnboarding,
19 | } as const;
20 |
--------------------------------------------------------------------------------
/src/hooks/use-theme-mode.tsx:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import { useQuery } from '@tanstack/react-query';
3 | import z from 'zod';
4 |
5 | import { STORAGE_KEY_THEME } from '@/lib/ficus-ui/theme';
6 |
7 | export const themeQueryKey = ['app-theme', STORAGE_KEY_THEME];
8 |
9 | const zTheme = () => z.enum(['system', 'light', 'dark']).default('system');
10 |
11 | export const useThemeMode = () => {
12 | return useQuery({
13 | queryKey: themeQueryKey,
14 | queryFn: () =>
15 | AsyncStorage.getItem(STORAGE_KEY_THEME).then((value) =>
16 | zTheme().parse(value)
17 | ),
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/src/layout/splash-screen-manager.tsx:
--------------------------------------------------------------------------------
1 | import { SplashScreen } from 'expo-router';
2 | import { PropsWithChildren, useEffect } from 'react';
3 |
4 | import { authClient } from '@/features/auth/client';
5 |
6 | export const SplashScreenManager = (props: PropsWithChildren) => {
7 | const session = authClient.useSession();
8 |
9 | const isAppReady = !session.isPending;
10 |
11 | // Manage splashscreen hide when all wanted content is loaded
12 | // For example session, fonts, required queries etc
13 | useEffect(() => {
14 | if (isAppReady) {
15 | SplashScreen.hide();
16 | }
17 | }, [isAppReady]);
18 |
19 | return props.children;
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-more-vertical.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-arrow-left.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-arrow-right.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-clock.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-dashboard.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-check-circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-search.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-x.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "moduleResolution": "Bundler",
6 | "module": "ESNext",
7 | "target": "ES2022",
8 | "skipLibCheck": true,
9 | "strict": true,
10 | "strictNullChecks": true,
11 | "noUncheckedIndexedAccess": true,
12 | "noImplicitAny": true,
13 | "baseUrl": ".",
14 | "paths": {
15 | "@/*": ["src/*"]
16 | },
17 | "typeRoots": ["node_modules/@types", "src/types"],
18 | "plugins": [
19 | {
20 | "name": "ts-plugin-sort-import-suggestions",
21 | "moveUpPatterns": ["@/", "\\.{1,2}/"]
22 | }
23 | ]
24 | },
25 | "include": ["src", ".expo/types/**/*.ts", "expo-env.d.ts"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/layout/view-tab-content.tsx:
--------------------------------------------------------------------------------
1 | import { Box, BoxProps, ScrollBox } from 'react-native-ficus-ui';
2 | import { useSafeAreaInsets } from 'react-native-safe-area-context';
3 |
4 | import { WITH_NATIVE_TABS } from '@/app/(logged)/(tabs)/_layout';
5 | import { isApple } from '@/constants/device';
6 |
7 | export const ViewTabContent = ({
8 | withHeader = isApple && WITH_NATIVE_TABS,
9 | children,
10 | ...props
11 | }: BoxProps & { withHeader?: boolean }) => {
12 | const insets = useSafeAreaInsets();
13 |
14 | return (
15 |
21 | {children}
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevrons-left.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevrons-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/lib/i18n/constants.ts:
--------------------------------------------------------------------------------
1 | import z from 'zod';
2 |
3 | import locales from '@/locales';
4 |
5 | export type Language = {
6 | key: keyof typeof locales;
7 | dir?: 'ltr' | 'rtl';
8 | fontScale?: number;
9 | };
10 |
11 | export const DEFAULT_NAMESPACE = 'common';
12 |
13 | export const DEFAULT_LANGUAGE_KEY: Language['key'] = 'en';
14 |
15 | export type LanguageKey = (typeof AVAILABLE_LANGUAGES)[number]['key'];
16 | export const AVAILABLE_LANGUAGES = [
17 | {
18 | key: 'en',
19 | } as const,
20 | {
21 | key: 'fr',
22 | } as const,
23 | ] satisfies Language[];
24 |
25 | export const zAvailableLanguages = () =>
26 | z
27 | .enum(AVAILABLE_LANGUAGES.map((lang) => lang.key))
28 | .default(DEFAULT_LANGUAGE_KEY);
29 |
30 | export const LANGUAGE_STORAGE_KEY = 'language';
31 |
--------------------------------------------------------------------------------
/src/features/app-onboarding/use-background-animated-style.tsx:
--------------------------------------------------------------------------------
1 | import { WINDOW_WIDTH } from '@gorhom/bottom-sheet';
2 | import {
3 | interpolate,
4 | SharedValue,
5 | useAnimatedStyle,
6 | } from 'react-native-reanimated';
7 |
8 | import { appOnboardingScreens } from '@/features/app-onboarding/view-app-onboarding';
9 |
10 | const backgroundImageSize = { width: 1536, height: 1024 };
11 |
12 | export const useBackgroundAnimatedStyle = (scrollX: SharedValue) => {
13 | return useAnimatedStyle(() => {
14 | const maxScrollX = WINDOW_WIDTH * (appOnboardingScreens.length - 1);
15 |
16 | const right = interpolate(
17 | scrollX.value,
18 | [0, maxScrollX],
19 | [650, Math.min(750, backgroundImageSize.width - WINDOW_WIDTH)]
20 | );
21 |
22 | return { right };
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-alert-circle.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/docs.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react';
2 | import { Button, ScrollBox, Stack } from 'react-native-ficus-ui';
3 |
4 | import * as icons from './generated';
5 |
6 | export default {
7 | title: 'Icons/Custom',
8 | };
9 |
10 | export const AllIcons = () => (
11 |
12 |
13 | {Object.entries(icons).map(([name, Icon]) => (
14 |
15 |
16 |
17 | ))}
18 |
19 |
20 | );
21 |
22 | const CustomIcon = ({
23 | children,
24 | name,
25 | }: {
26 | children: ReactElement;
27 | name: string;
28 | }) => {
29 | return (
30 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | BoxProps,
4 | HStack,
5 | StackProps,
6 | Text,
7 | TextProps,
8 | } from 'react-native-ficus-ui';
9 |
10 | export const Card = (props: BoxProps) => {
11 | return (
12 |
20 | );
21 | };
22 |
23 | export const CardHeader = (props: StackProps) => {
24 | return ;
25 | };
26 |
27 | export const CardTitle = (props: TextProps) => {
28 | return ;
29 | };
30 |
31 | export const CardBody = (props: BoxProps) => {
32 | return ;
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-chevrons-up-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/input.ts:
--------------------------------------------------------------------------------
1 | import { defineStyleConfig, Dict, InputProps } from 'react-native-ficus-ui';
2 |
3 | export default defineStyleConfig<
4 | InputProps,
5 | Dict,
6 | Dict
7 | >({
8 | defaultProps: { variant: 'default', size: 'lg' },
9 | baseStyle: {
10 | borderRadius: 'md',
11 | _disabled: { opacity: 0.7 },
12 | _focused: { borderWidth: 2 },
13 | },
14 | sizes: { sm: { h: 32 }, md: { h: 36 }, lg: { h: 40 } },
15 | variants: {
16 | default: {
17 | borderWidth: 1,
18 | borderColor: 'neutral.300',
19 | placeholderTextColor: 'neutral.600',
20 | backgroundColor: 'white',
21 | _dark: {
22 | borderColor: 'neutral.600',
23 | backgroundColor: 'neutral.950',
24 | placeholderTextColor: 'neutral.300',
25 | },
26 | },
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/src/lib/i18n/config.ts:
--------------------------------------------------------------------------------
1 | import { InitOptions } from 'i18next';
2 |
3 | import { DEFAULT_LANGUAGE_KEY, DEFAULT_NAMESPACE } from '@/lib/i18n/constants';
4 |
5 | import locales from '@/locales';
6 |
7 | export const i18nConfig = (
8 | language: keyof typeof locales | undefined
9 | ): InitOptions => ({
10 | compatibilityJSON: 'v4',
11 | defaultNS: DEFAULT_NAMESPACE,
12 | ns: Object.keys(locales[DEFAULT_LANGUAGE_KEY]),
13 | lng: language,
14 | resources: locales,
15 | fallbackLng: DEFAULT_LANGUAGE_KEY,
16 | supportedLngs: Object.keys(locales),
17 |
18 | // Fix issue with i18next types
19 | // https://www.i18next.com/overview/typescript#argument-of-type-defaulttfuncreturn-is-not-assignable-to-parameter-of-type-xyz
20 | returnNull: false,
21 |
22 | interpolation: {
23 | escapeValue: false, // react already safes from xss
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/src/components/ui/animated-step-indicator.tsx:
--------------------------------------------------------------------------------
1 | import { ViewProps, ViewStyle } from 'react-native';
2 | import Animated from 'react-native-reanimated';
3 |
4 | type AnimatedStepIndicatorProps = { isActive?: boolean };
5 |
6 | export const AnimatedStepIndicator = (
7 | props: AnimatedStepIndicatorProps &
8 | Omit & { style?: ViewStyle }
9 | ) => {
10 | return (
11 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconPanel.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconPanel = ficus(_Svg);
21 | export default IconPanel;
22 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-house-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/features/books/book-cover.tsx:
--------------------------------------------------------------------------------
1 | import { Box, BoxProps, Text } from 'react-native-ficus-ui';
2 |
3 | import { BookGetByIdResponse } from '@/lib/hey-api/generated';
4 |
5 | export type BookCoverProps = BoxProps & { book: BookGetByIdResponse };
6 |
7 | const COVER_HEIGHT = 240;
8 |
9 | export const BookCover = ({
10 | book,
11 | h = COVER_HEIGHT,
12 | ...props
13 | }: BookCoverProps) => {
14 | return (
15 |
25 |
26 | {book.title}
27 |
28 |
29 | {book.author}
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/features/auth/auth-header.tsx:
--------------------------------------------------------------------------------
1 | import { HStack } from 'react-native-ficus-ui';
2 | import { useSafeAreaInsets } from 'react-native-safe-area-context';
3 |
4 | import { Logo } from '@/components/icons/generated';
5 | import { LocaleSwitcher } from '@/components/ui/locale-switcher';
6 | import { ThemeSwitcher } from '@/components/ui/theme-switcher';
7 |
8 | export const AuthHeader = () => {
9 | const insets = useSafeAreaInsets();
10 |
11 | return (
12 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/features/auth/client.ts:
--------------------------------------------------------------------------------
1 | import { expoClient } from '@better-auth/expo/client';
2 | import appConfig from 'app.config';
3 | import {
4 | emailOTPClient,
5 | inferAdditionalFields,
6 | } from 'better-auth/client/plugins';
7 | import { createAuthClient } from 'better-auth/react';
8 | import * as SecureStore from 'expo-secure-store';
9 |
10 | const authBaseURL: string =
11 | process.env.EXPO_PUBLIC_AUTH_URL ??
12 | `${process.env.EXPO_PUBLIC_BASE_URL}/api/auth`;
13 |
14 | export const authClient = createAuthClient({
15 | baseURL: authBaseURL,
16 | plugins: [
17 | expoClient({
18 | scheme: appConfig.scheme,
19 | storagePrefix: appConfig.scheme,
20 | storage: SecureStore,
21 | }),
22 | emailOTPClient(),
23 | inferAdditionalFields({
24 | user: {
25 | onboardedAt: {
26 | type: 'date',
27 | },
28 | },
29 | }),
30 | ],
31 | });
32 |
--------------------------------------------------------------------------------
/.github/workflows/eas-build.yml:
--------------------------------------------------------------------------------
1 | name: 🔨 EAS build
2 |
3 | env:
4 | EXPO_PUBLIC_API_URL: ${{ vars.API_URL }}
5 | EXPO_PUBLIC_AUTH_URL: ${{ vars.AUTH_URL }}
6 | EXPO_PUBLIC_OPENAPI_URL: ${{ vars.OPENAPI_URL }}
7 |
8 | on:
9 | push:
10 | branches: [main]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: 🏗 Setup repo
17 | uses: actions/checkout@v5
18 |
19 | - name: 🏗 Setup Node
20 | uses: actions/setup-node@v5
21 | with:
22 | node-version: 'lts/*'
23 | cache: 'pnpm'
24 |
25 | - name: 🏗 Setup EAS
26 | uses: expo/expo-github-action@v8
27 | with:
28 | eas-version: latest
29 | token: ${{ secrets.EXPO_TOKEN }}
30 |
31 | - name: 📦 Install dependencies
32 | run: pnpm install
33 |
34 | - name: 🚀 Build app
35 | run: eas build --non-interactive
36 |
--------------------------------------------------------------------------------
/src/components/theme-manager.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import { useEffect } from 'react';
3 | import { useColorScheme } from 'react-native';
4 | import { useColorMode } from 'react-native-ficus-ui';
5 |
6 | import { useThemeMode } from '@/hooks/use-theme-mode';
7 |
8 | export const ThemeManager = () => {
9 | const colorScheme = useColorScheme();
10 | const ficusColorMode = useColorMode();
11 |
12 | const setFicusColorMode = ficusColorMode.setColorMode;
13 |
14 | const themeQuery = useThemeMode();
15 | const currentTheme = themeQuery.data ?? 'system';
16 |
17 | useEffect(() => {
18 | if (colorScheme) {
19 | setFicusColorMode(currentTheme === 'system' ? colorScheme : currentTheme);
20 | }
21 | }, [currentTheme, colorScheme, setFicusColorMode]);
22 |
23 | return (
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/app/+not-found.tsx:
--------------------------------------------------------------------------------
1 | import { Link, usePathname } from 'expo-router';
2 | import { useTranslation } from 'react-i18next';
3 | import { Box, Button, Center, Text } from 'react-native-ficus-ui';
4 |
5 | import { ViewSafeContent } from '@/layout/view-safe-content';
6 |
7 | export default function NotFound() {
8 | const { t } = useTranslation(['layout']);
9 |
10 | const path = usePathname();
11 |
12 | return (
13 |
14 |
15 | {t('layout:notFound.title')}
16 |
17 |
18 |
19 |
20 |
21 | {process.env.NODE_ENV === 'development' && (
22 | {path}
23 | )}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-user.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/workflows/eas-update.yml:
--------------------------------------------------------------------------------
1 | name: 🚀 EAS update
2 |
3 | env:
4 | EXPO_PUBLIC_API_URL: ${{ vars.API_URL }}
5 | EXPO_PUBLIC_AUTH_URL: ${{ vars.AUTH_URL }}
6 | EXPO_PUBLIC_OPENAPI_URL: ${{ vars.OPENAPI_URL }}
7 |
8 | on:
9 | push:
10 | branches: [main]
11 |
12 | jobs:
13 | update:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: 🏗 Setup repo
17 | uses: actions/checkout@v5
18 |
19 | - name: 🏗 Setup Node
20 | uses: actions/setup-node@v5
21 | with:
22 | node-version: 'lts/*'
23 | cache: 'pnpm'
24 |
25 | - name: 🏗 Setup EAS
26 | uses: expo/expo-github-action@v8
27 | with:
28 | eas-version: latest
29 | token: ${{ secrets.EXPO_TOKEN }}
30 |
31 | - name: 📦 Install dependencies
32 | run: pnpm install
33 |
34 | - name: 🚀 Create update
35 | run: eas update --auto --non-interactive
36 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-lock.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-folder.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # Expo
12 | .expo/
13 | dist/
14 | web-build/
15 | expo-env.d.ts
16 |
17 | # Native
18 | .kotlin/
19 | *.orig.*
20 | *.jks
21 | *.p8
22 | *.p12
23 | *.key
24 | *.mobileprovision
25 |
26 | # Metro
27 | .metro-health-check*
28 |
29 | # debug
30 | npm-debug.log*
31 | yarn-debug.log*
32 | yarn-error.log*
33 |
34 | # misc
35 | .DS_Store
36 | *.pem
37 | /.vscode/*
38 | !/.vscode/extensions.json
39 | !/.vscode/settings.example.json
40 | .idea/
41 | .eslintcache
42 | .db
43 | tsconfig.tsbuildinfo
44 |
45 | # local env files
46 | .env
47 | .env.local
48 | .env.development.local
49 | .env.test.local
50 | .env.production.local
51 |
52 | # local builds
53 | android/
54 | ios/
55 |
56 | # hey-api
57 | src/lib/hey-api/generated/*
58 | openapi-ts-error-*.log
59 |
60 | .tanstack
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-home.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-mail.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-calendar.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCheck.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconCheck = ficus(_Svg);
23 | export default IconCheck;
24 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form-submit.tsx:
--------------------------------------------------------------------------------
1 | import { ActivityIndicator } from 'react-native';
2 | import { Button, ButtonProps } from 'react-native-ficus-ui';
3 |
4 | import { useFormContext } from '@/lib/tanstack-form/context';
5 |
6 | export default function FormSubmit({
7 | submitting,
8 | children,
9 | ...props
10 | }: Readonly<
11 | ButtonProps & {
12 | submitting?: React.ReactElement;
13 | children: React.ReactNode;
14 | }
15 | >) {
16 | const form = useFormContext();
17 | return (
18 | [state.canSubmit, state.isSubmitting]}>
19 | {([canSubmit, isSubmitting]) => (
20 |
28 | )}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-file.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronUp.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconChevronUp = ficus(_Svg);
23 | export default IconChevronUp;
24 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconPlay.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconPlay = ficus(_Svg);
23 | export default IconPlay;
24 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronDown.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconChevronDown = ficus(_Svg);
23 | export default IconChevronDown;
24 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronLeft.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconChevronLeft = ficus(_Svg);
23 | export default IconChevronLeft;
24 |
--------------------------------------------------------------------------------
/src/lib/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import dayjs from 'dayjs';
3 | import { getLocales } from 'expo-localization';
4 | import i18n from 'i18next';
5 | import { initReactI18next } from 'react-i18next';
6 |
7 | import { i18nConfig } from '@/lib/i18n/config';
8 | import {
9 | LANGUAGE_STORAGE_KEY,
10 | zAvailableLanguages,
11 | } from '@/lib/i18n/constants';
12 |
13 | const initI18n = async () => {
14 | const language = await AsyncStorage.getItem(LANGUAGE_STORAGE_KEY);
15 |
16 | i18n
17 | .use(initReactI18next)
18 | .init(
19 | i18nConfig(
20 | zAvailableLanguages().parse(language ?? getLocales()[0]?.languageCode)
21 | )
22 | );
23 | };
24 |
25 | initI18n();
26 |
27 | export const syncLanguage = (langKey: string) => {
28 | dayjs.locale(langKey);
29 | AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, langKey);
30 | };
31 |
32 | i18n.on('languageChanged', (langKey) => syncLanguage(langKey));
33 |
34 | export default i18n;
35 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronRight.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconChevronRight = ficus(_Svg);
23 | export default IconChevronRight;
24 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-repository.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-x-circle.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconPlus.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconPlus = ficus(_Svg);
29 | export default IconPlus;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconArrowDown.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconArrowDown = ficus(_Svg);
23 | export default IconArrowDown;
24 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconArrowUp.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconArrowUp = ficus(_Svg);
23 | export default IconArrowUp;
24 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/context.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createFormHookContexts,
3 | ReactFormExtendedApi,
4 | } from '@tanstack/react-form';
5 |
6 | const {
7 | fieldContext,
8 | useFieldContext,
9 | formContext,
10 | useFormContext: useTanstackFormContext,
11 | } = createFormHookContexts();
12 |
13 | const useFormContext = >() => {
14 | const form = useTanstackFormContext();
15 |
16 | return form as unknown as ReactFormExtendedApi<
17 | Fields,
18 | undefined,
19 | undefined,
20 | undefined,
21 | undefined,
22 | undefined,
23 | undefined,
24 | undefined,
25 | undefined,
26 | undefined,
27 | undefined,
28 | undefined
29 | >;
30 | };
31 |
32 | // type WithForm = <
33 | // Fields,
34 | // undefined,
35 | // undefined,
36 | // undefined,
37 | // undefined,
38 | // undefined,
39 | // undefined,
40 | // undefined,
41 | // undefined,
42 | // undefined,
43 | // Props
44 | // >
45 |
46 | export { fieldContext, formContext, useFieldContext, useFormContext };
47 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-house.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-circle-user-round.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-copy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-book-open.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/form/docs.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ScrollBox, Stack } from 'react-native-ficus-ui';
2 |
3 | import { useAppForm } from '@/lib/tanstack-form/config';
4 |
5 | export default {
6 | title: 'Form/Fields',
7 | };
8 |
9 | export const AllFields = () => {
10 | const form = useAppForm({ defaultValues: { text: '', otp: '' } });
11 | return (
12 |
13 |
14 |
15 |
16 | {(field) => (
17 |
18 | FieldText
19 |
20 |
21 | )}
22 |
23 |
24 | {(field) => (
25 |
26 | FieldOtp
27 |
28 |
29 | )}
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form-field.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode, useEffect, useId } from 'react';
2 | import { Box } from 'react-native-ficus-ui';
3 |
4 | import { useFieldContext } from '@/lib/tanstack-form/context';
5 |
6 | type FormFieldSize = 'sm' | 'default' | 'lg';
7 |
8 | export type FieldContextMeta = ReturnType<
9 | ReturnType['getMeta']
10 | > & {
11 | id: string;
12 | descriptionId: string;
13 | errorId: string;
14 | size?: FormFieldSize;
15 | };
16 |
17 | export const FormField = (props: {
18 | id?: string;
19 | size?: FormFieldSize;
20 | children?: ReactNode;
21 | }) => {
22 | const _id = useId();
23 | const id = props.id ?? _id;
24 |
25 | const field = useFieldContext();
26 |
27 | const setFieldMeta = field.setMeta;
28 |
29 | useEffect(() => {
30 | setFieldMeta((meta) => ({
31 | ...meta,
32 | id,
33 | descriptionId: `${id}-description`,
34 | errorId: `${id}-error`,
35 | size: props.size,
36 | }));
37 | }, [setFieldMeta, id, props.size]);
38 |
39 | return {props.children};
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-hash.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-trash-2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-devices.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/locales/en/auth.json:
--------------------------------------------------------------------------------
1 | {
2 | "signin": {
3 | "title": "Login to your account",
4 | "subtitle": "Enter your email to login to your account",
5 | "email": { "placeholder": "Email" },
6 | "loginWithEmail": "Login with email",
7 | "or": "or",
8 | "loginWithGithub": "Login with GitHub"
9 | },
10 | "verification": {
11 | "back": "Back",
12 | "title": "Verification",
13 | "description": "If you have an account, we have sent a code to ",
14 | "enterItBelow": "Enter it below.",
15 | "verificationCode": { "label": "Verification code" },
16 | "expireHint": "The code expires shortly",
17 | "confirm": "Confirm"
18 | },
19 | "onboarding": {
20 | "title": "Welcome",
21 | "subtitle": "Let's personalize your experience",
22 | "name": { "label": "What is your name?" },
23 | "continue": "Continue"
24 | },
25 | "signOut": {
26 | "action": "Sign Out",
27 | "confirm": {
28 | "title": "Account Sign Out",
29 | "description": "You are about to end your session",
30 | "errorMessage": "Failed to Sign Out",
31 | "cancel": "Cancel",
32 | "signOut": "Sign out"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCreditCard.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconCreditCard = ficus(_Svg);
29 | export default IconCreditCard;
30 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-log-out.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconDashboard.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconDashboard = ficus(_Svg);
21 | export default IconDashboard;
22 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCheckCircle.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconCheckCircle = ficus(_Svg);
21 | export default IconCheckCircle;
22 |
--------------------------------------------------------------------------------
/src/types/utilities.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/redundant-type-aliases */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | /**
4 | * Use this type to temporary bypass to `any` without writting `any`
5 | * Comment the line to find where it's used
6 | */
7 | type TODO = any;
8 |
9 | /**
10 | * Use this type to use an explicit `any`
11 | */
12 | type ExplicitAny = any;
13 |
14 | /**
15 | * Use this type to remove keys from T that are in U type.
16 | */
17 | type RemoveFromType = Pick>;
18 |
19 | /**
20 | * Use this type to overwrite the keys of the first type with the second one.
21 | * This is mainly useful with custom props type that extends multiple components
22 | * with the `as` props.
23 | */
24 | type Overwrite = RemoveFromType & U;
25 |
26 | type UnionKeys = T extends T ? keyof T : never;
27 | type StrictUnionHelper = T extends ExplicitAny
28 | ? T & Partial, keyof T>, undefined>>
29 | : never;
30 | type StrictUnion = StrictUnionHelper;
31 |
32 | /**
33 | * Clean up type for better DX
34 | */
35 | type Prettify = {
36 | [K in keyof T]: T[K];
37 | } & {};
38 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-download.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-log-in.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-share.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-upload.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-unlock.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/features/app-onboarding/use-mascot-animated-style.tsx:
--------------------------------------------------------------------------------
1 | import { WINDOW_HEIGHT, WINDOW_WIDTH } from '@gorhom/bottom-sheet';
2 | import {
3 | interpolate,
4 | SharedValue,
5 | useAnimatedStyle,
6 | } from 'react-native-reanimated';
7 |
8 | import { appOnboardingScreens } from '@/features/app-onboarding/view-app-onboarding';
9 |
10 | export const useMascotAnimatedStyle = (scrollX: SharedValue) => {
11 | return useAnimatedStyle(() => {
12 | const maxScrollX = WINDOW_WIDTH * (appOnboardingScreens.length - 1);
13 |
14 | const leftStart = WINDOW_WIDTH / 3.5;
15 | const topStart = WINDOW_HEIGHT / 2.5;
16 |
17 | const left = interpolate(
18 | scrollX.value,
19 | [0, maxScrollX],
20 | [leftStart, leftStart - 50]
21 | );
22 |
23 | const top = interpolate(
24 | scrollX.value,
25 | [0, maxScrollX],
26 | [topStart, topStart + 80]
27 | );
28 |
29 | const rotate = interpolate(scrollX.value, [0, maxScrollX], [2, 10]);
30 |
31 | const scale = interpolate(scrollX.value, [0, maxScrollX], [1, 0.7]);
32 |
33 | return {
34 | left,
35 | top,
36 | transform: [{ rotate: `${rotate}deg` }, { scale }],
37 | };
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-tag.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/hooks/use-share/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | MutateOptions,
3 | useMutation,
4 | UseMutationOptions,
5 | } from '@tanstack/react-query';
6 | import { Share, ShareAction, ShareContent, ShareOptions } from 'react-native';
7 | import { toast } from 'sonner-native';
8 |
9 | type UseShareMutationParameters = {
10 | content: ShareContent;
11 | options?: ShareOptions;
12 | };
13 |
14 | export const useShare = (
15 | mutationOptions?: UseMutationOptions<
16 | ShareAction,
17 | Error,
18 | UseShareMutationParameters
19 | >
20 | ) => {
21 | const mutation = useMutation({
22 | ...mutationOptions,
23 | mutationFn: async ({ options, content }) => {
24 | return Share.share(content, options);
25 | },
26 | onError: (error, ...params) => {
27 | toast.error(error.name);
28 | mutationOptions?.onError?.(error, ...params);
29 | },
30 | });
31 |
32 | return {
33 | ...mutation,
34 | open: (
35 | content: ShareContent,
36 | options?: ShareOptions,
37 | mutationOptions?: MutateOptions<
38 | ShareAction,
39 | Error,
40 | UseShareMutationParameters
41 | >
42 | ) => mutation.mutate({ content, options }, mutationOptions),
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/src/lib/tanstack-form/components/form-field-error.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from '@tanstack/react-form';
2 | import { HStack, Text, TextProps } from 'react-native-ficus-ui';
3 |
4 | import { FieldContextMeta } from '@/lib/tanstack-form/components/form-field';
5 | import { useFieldContext } from '@/lib/tanstack-form/context';
6 |
7 | import { IconAlertCircle } from '@/components/icons/generated';
8 |
9 | export const FormFieldError = (props: TextProps) => {
10 | const field = useFieldContext();
11 |
12 | const meta = useStore(field.store, (state) => {
13 | const fieldMeta = state.meta as FieldContextMeta;
14 | return {
15 | errorMessage: fieldMeta.errors[0]?.message,
16 | errorId: fieldMeta.errorId,
17 | };
18 | });
19 |
20 | if (!meta.errorMessage) {
21 | return null;
22 | }
23 |
24 | return (
25 |
26 |
27 |
35 | {meta.errorMessage}
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronsLeft.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconChevronsLeft = ficus(_Svg);
21 | export default IconChevronsLeft;
22 |
--------------------------------------------------------------------------------
/src/app/(public)/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack, useRouter } from 'expo-router';
2 | import { useEffect } from 'react';
3 |
4 | import { useThemedStyle } from '@/hooks/use-themed-style';
5 |
6 | import { authClient } from '@/features/auth/client';
7 |
8 | export default function PublicLayout() {
9 | const router = useRouter();
10 | const session = authClient.useSession();
11 |
12 | const themedStyle = useThemedStyle();
13 |
14 | useEffect(() => {
15 | if (!session.isPending && session.data?.user) {
16 | router.replace('/(logged)/(tabs)/home');
17 | }
18 | }, [router, session.data?.user, session.isPending]);
19 |
20 | return (
21 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronsRight.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconChevronsRight = ficus(_Svg);
21 | export default IconChevronsRight;
22 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconArrowLeft.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconArrowLeft = ficus(_Svg);
29 | export default IconArrowLeft;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconArrowRight.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconArrowRight = ficus(_Svg);
29 | export default IconArrowRight;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconClock.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconClock = ficus(_Svg);
29 | export default IconClock;
30 |
--------------------------------------------------------------------------------
/src/locales/fr/auth.json:
--------------------------------------------------------------------------------
1 | {
2 | "signin": {
3 | "title": "Connexion à votre compte",
4 | "subtitle": "Entrez votre email ci-dessous pour vous connecter à votre compte",
5 | "email": { "placeholder": "Email" },
6 | "loginWithEmail": "Connexion avec email",
7 | "or": "ou",
8 | "loginWithGithub": "Connexion avec GitHub"
9 | },
10 | "verification": {
11 | "back": "Retour",
12 | "title": "Vérification",
13 | "description": "Si vous avez un compte, nous avons envoyé un code à ",
14 | "enterItBelow": "Entrez-le ci-dessous.",
15 | "verificationCode": { "label": "Code de vérification" },
16 | "expireHint": "Le code expire bientôt",
17 | "confirm": "Confirmer"
18 | },
19 | "onboarding": {
20 | "title": "Bienvenue",
21 | "subtitle": "Personnalisons votre expérience",
22 | "name": { "label": "Quel est votre nom ?" },
23 | "continue": "Continuer"
24 | },
25 | "signOut": {
26 | "action": "Déconnexion",
27 | "confirm": {
28 | "description": "Vous êtes sur le point de mettre fin à votre session",
29 | "errorMessage": "Échec de la déconnexion",
30 | "title": "Déconnexion du compte",
31 | "cancel": "Annuler",
32 | "signOut": "Déconnexion"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconSearch.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconSearch = ficus(_Svg);
29 | export default IconSearch;
30 |
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import Constants from 'expo-constants';
2 | import { Redirect, router, useFocusEffect } from 'expo-router';
3 | import { useCallback } from 'react';
4 |
5 | import { FullLoader } from '@/components/ui/full-loader';
6 |
7 | import { useOnboardingStore } from '@/features/app-onboarding/store';
8 | import { ViewOnboarding } from '@/features/app-onboarding/view-app-onboarding';
9 | import { authClient } from '@/features/auth/client';
10 |
11 | export default function Index() {
12 | const session = authClient.useSession();
13 | const isOnboarded = useOnboardingStore((state) => state.done);
14 |
15 | // Manage first app redirection
16 | useFocusEffect(
17 | useCallback(() => {
18 | if (!isOnboarded || session.isPending) {
19 | return;
20 | }
21 | router.replace(
22 | session.data?.user?.id ? '/(logged)/(tabs)/home' : '/(public)/sign-in'
23 | );
24 | }, [session.data, session.isPending, isOnboarded])
25 | );
26 |
27 | if (!isOnboarded) {
28 | return ;
29 | }
30 |
31 | if (Constants.expoConfig?.extra?.isStorybook) {
32 | return ;
33 | }
34 |
35 | if (session.isPending) {
36 | return ;
37 | }
38 |
39 | return <>>;
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconX.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconX = ficus(_Svg);
29 | export default IconX;
30 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-external-link.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconMoreVertical.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconMoreVertical = ficus(_Svg);
35 | export default IconMoreVertical;
36 |
--------------------------------------------------------------------------------
/.github/workflows/eas-preview.yml:
--------------------------------------------------------------------------------
1 | name: 📱 EAS Preview
2 |
3 | env:
4 | EXPO_PUBLIC_API_URL: ${{ vars.API_URL }}
5 | EXPO_PUBLIC_AUTH_URL: ${{ vars.AUTH_URL }}
6 | EXPO_PUBLIC_OPENAPI_URL: ${{ vars.OPENAPI_URL }}
7 |
8 | on: [pull_request]
9 | jobs:
10 | preview:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: 🏗 Setup repo
14 | uses: actions/checkout@v4
15 |
16 | - name: 🏗 Setup pnpm
17 | uses: pnpm/action-setup@v4
18 | with:
19 | version: 10
20 |
21 | - name: 🏗 Setup Node
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: 'lts/*'
25 |
26 | - name: 🏗 Setup EAS
27 | uses: expo/expo-github-action@v8
28 | with:
29 | eas-version: latest
30 | token: ${{ secrets.EXPO_TOKEN }}
31 |
32 | - name: 📦 Install dependencies
33 | run: pnpm install
34 |
35 | - name: 🚀 Create preview
36 | uses: expo/expo-github-action/preview@v8
37 | with:
38 | # `github.event.pull_request.head.ref` is only available on `pull_request` triggers.
39 | # Use your own, or keep the automatically inferred branch name from `--auto`, when using different triggers.
40 | command: eas update --auto --branch ${{ github.event.pull_request.head.ref }}
41 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconChevronsUpDown.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconChevronsUpDown = ficus(_Svg);
21 | export default IconChevronsUpDown;
22 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-languages.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-power.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconHouseFill.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconHouseFill = ficus(_Svg);
21 | export default IconHouseFill;
22 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-edit-3.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/svgr.config.cjs:
--------------------------------------------------------------------------------
1 | const comment =
2 | '// Autogenerated file, DO NOT MODIFY or it will be overwritten';
3 |
4 | const template = (variables, { tpl }) => {
5 | const componentName = variables.componentName.replace('Svg', '');
6 | return tpl`
7 | ${comment}
8 | ${variables.imports};
9 |
10 | ${variables.interfaces};
11 | import { ficus } from 'react-native-ficus-ui';
12 |
13 | const _Svg = (${variables.props}) => (
14 | ${variables.jsx}
15 | );
16 |
17 | const ${componentName} = ficus(_Svg)
18 |
19 | export default ${componentName};
20 | `;
21 | };
22 |
23 | // eslint-disable-next-line no-undef
24 | module.exports = {
25 | outDir: './src/components/icons/generated',
26 | typescript: true,
27 | replaceAttrValues: {
28 | '#000': 'currentColor',
29 | none: 'currentColor',
30 | black: 'currentColor',
31 | },
32 | expandProps: true,
33 | template,
34 | native: true,
35 | svgo: true,
36 | svgoConfig: {
37 | plugins: [
38 | {
39 | name: 'removeAttrs',
40 | params: {
41 | attrs: '(xmlns)',
42 | },
43 | },
44 | ],
45 | },
46 |
47 | // Prevent svgr + prettier 3 compatibility issue
48 | // It is ok to disable it here, generation script will run prettier
49 | // See https://github.com/gregberge/svgr/issues/893 for details on the underlying issue
50 | prettier: false,
51 | };
52 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconFolder.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconFolder = ficus(_Svg);
23 | export default IconFolder;
24 |
--------------------------------------------------------------------------------
/src/hooks/use-browser/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | MutateOptions,
3 | useMutation,
4 | UseMutationOptions,
5 | } from '@tanstack/react-query';
6 | import * as WebBrowser from 'expo-web-browser';
7 | import { toast } from 'sonner-native';
8 |
9 | type UseBrowserMutationParameters = {
10 | url: string;
11 | options?: WebBrowser.WebBrowserOpenOptions;
12 | };
13 |
14 | export const useBrowser = (
15 | mutationOptions?: UseMutationOptions<
16 | WebBrowser.WebBrowserResult,
17 | Error,
18 | UseBrowserMutationParameters
19 | >
20 | ) => {
21 | const mutation = useMutation({
22 | ...mutationOptions,
23 | mutationFn: async ({ options, url }) => {
24 | return WebBrowser.openBrowserAsync(url, {
25 | ...options,
26 | dismissButtonStyle: 'close',
27 | presentationStyle: WebBrowser.WebBrowserPresentationStyle.FORM_SHEET,
28 | });
29 | },
30 | onError: (error, ...params) => {
31 | toast.error(error.name);
32 | mutationOptions?.onError?.(error, ...params);
33 | },
34 | });
35 |
36 | return {
37 | ...mutation,
38 | open: (
39 | url: string,
40 | options?: WebBrowser.WebBrowserOpenOptions,
41 | mutationOptions?: MutateOptions<
42 | WebBrowser.WebBrowserResult,
43 | Error,
44 | UseBrowserMutationParameters
45 | >
46 | ) => mutation.mutate({ url, options }, mutationOptions),
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-book-open-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconAlertCircle.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconAlertCircle = ficus(_Svg);
35 | export default IconAlertCircle;
36 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/badge.ts:
--------------------------------------------------------------------------------
1 | import { BadgeProps, defineStyleConfig, Dict } from 'react-native-ficus-ui';
2 |
3 | export default defineStyleConfig<
4 | BadgeProps,
5 | Dict,
6 | Dict
7 | >({
8 | defaultProps: { variant: '@primary', size: 'md' },
9 | sizes: {
10 | xs: { h: 16, fontSize: '2xs', fontWeight: 'medium' },
11 | sm: { h: 20, fontSize: 'xs', fontWeight: 'medium' },
12 | md: { h: 24, fontSize: 'sm', fontWeight: 'medium' },
13 | lg: { h: 32, fontSize: 'md', fontWeight: 'medium' },
14 | },
15 | baseStyle: {
16 | px: 8,
17 | textTransform: 'none',
18 | borderRadius: 'md',
19 | },
20 | variants: {
21 | '@primary': {
22 | backgroundColor: 'brand.900',
23 | color: 'white',
24 | _dark: {
25 | backgroundColor: 'brand.50',
26 | color: 'brand.900',
27 | },
28 | },
29 | '@secondary': {
30 | backgroundColor: 'white',
31 | color: 'brand.950',
32 | _dark: {
33 | backgroundColor: 'brand.800',
34 | color: 'brand.50',
35 | },
36 | },
37 | '@error': {
38 | backgroundColor: 'negative.100',
39 | color: 'negative.800',
40 | },
41 | '@warning': {
42 | backgroundColor: 'warning.100',
43 | color: 'warning.800',
44 | },
45 | '@success': {
46 | backgroundColor: 'positive.100',
47 | color: 'positive.600',
48 | },
49 | },
50 | });
51 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconUser.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconUser = ficus(_Svg);
29 | export default IconUser;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconLock.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconLock = ficus(_Svg);
29 | export default IconLock;
30 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-shopping-cart.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconRepository.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
21 | );
22 | const IconRepository = ficus(_Svg);
23 | export default IconRepository;
24 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-moon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconHome.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconHome = ficus(_Svg);
29 | export default IconHome;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconMail.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconMail = ficus(_Svg);
29 | export default IconMail;
30 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-eye.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconHouse.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconHouse = ficus(_Svg);
21 | export default IconHouse;
22 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconFile.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconFile = ficus(_Svg);
29 | export default IconFile;
30 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-eye-closed.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCircleUserRound.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconCircleUserRound = ficus(_Svg);
21 | export default IconCircleUserRound;
22 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-house-duotone.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/workflows/code-quality.yml:
--------------------------------------------------------------------------------
1 | name: 🔎 Code Quality
2 |
3 | env:
4 | EXPO_PUBLIC_API_URL: ${{ vars.API_URL }}
5 | EXPO_PUBLIC_AUTH_URL: ${{ vars.AUTH_URL }}
6 | EXPO_PUBLIC_OPENAPI_URL: ${{ vars.OPENAPI_URL }}
7 |
8 | on:
9 | push:
10 | branches: [main]
11 | pull_request:
12 |
13 | jobs:
14 | linter:
15 | name: 🧹 Linter
16 | timeout-minutes: 10
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: 🏗 Setup repo
20 | uses: actions/checkout@v4
21 |
22 | - name: 🏗 Setup Node.js
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: 'lts/*'
26 |
27 | - name: 📦 Install pnpm
28 | uses: pnpm/action-setup@v4
29 | with:
30 | version: 10
31 |
32 | - name: 📦 Install dependencies
33 | run: pnpm install
34 |
35 | - name: Run eslint
36 | run: pnpm run lint:eslint
37 |
38 | typescriptChecker:
39 | name: 🟦 TypeScript Checker
40 | timeout-minutes: 10
41 | runs-on: ubuntu-latest
42 | strategy:
43 | matrix:
44 | node: [22, 'lts/*']
45 | steps:
46 | - uses: actions/checkout@v4
47 |
48 | - name: Install pnpm
49 | uses: pnpm/action-setup@v4
50 | with:
51 | version: 10
52 |
53 | - name: Setup Node.js
54 | uses: actions/setup-node@v4
55 | with:
56 | node-version: ${{ matrix.node }}
57 | cache: 'pnpm'
58 |
59 | - name: Install deps
60 | run: pnpm install
61 |
62 | - name: Run Typescript checker
63 | run: pnpm run lint:ts
64 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconTrash2.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconTrash2 = ficus(_Svg);
21 | export default IconTrash2;
22 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconDevices.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconDevices = ficus(_Svg);
21 | export default IconDevices;
22 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-share-2.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCopy.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconCopy = ficus(_Svg);
29 | export default IconCopy;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconBookOpen.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconBookOpen = ficus(_Svg);
29 | export default IconBookOpen;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconXCircle.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconXCircle = ficus(_Svg);
35 | export default IconXCircle;
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconCalendar.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
39 | );
40 | const IconCalendar = ficus(_Svg);
41 | export default IconCalendar;
42 |
--------------------------------------------------------------------------------
/src/lib/ficus-ui/components/button.ts:
--------------------------------------------------------------------------------
1 | import { ButtonProps, defineStyleConfig, Dict } from 'react-native-ficus-ui';
2 |
3 | export default defineStyleConfig<
4 | ButtonProps,
5 | Dict,
6 | Dict
7 | >({
8 | defaultProps: { variant: '@primary', size: 'lg' },
9 | sizes: {
10 | xs: { h: 28, fontSize: 'xs', fontWeight: 'medium' },
11 | sm: { h: 32, fontSize: 'sm', fontWeight: 'medium' },
12 | md: { h: 36, fontSize: 'sm', fontWeight: 'medium' },
13 | lg: { h: 40, fontSize: 'sm', fontWeight: 'medium' },
14 | },
15 | baseStyle: {
16 | gap: 4,
17 | borderRadius: 'md',
18 | borderColor: 'brand.200',
19 | _disabled: { opacity: 0.7 },
20 | _dark: {
21 | borderColor: 'brand.600',
22 | },
23 | },
24 | variants: {
25 | '@primary': {
26 | backgroundColor: 'brand.900',
27 | color: 'white',
28 | _dark: {
29 | backgroundColor: 'brand.50',
30 | color: 'brand.900',
31 | },
32 | },
33 | '@secondary': {
34 | backgroundColor: 'white',
35 | color: 'brand.950',
36 | borderWidth: 1,
37 | _dark: {
38 | backgroundColor: 'brand.800',
39 | color: 'brand.50',
40 | },
41 | },
42 | '@destructive': {
43 | backgroundColor: 'negative.600',
44 | color: 'white',
45 | },
46 | '@ghost': {
47 | backgroundColor: 'transparent',
48 | color: 'neutral.950',
49 | _dark: { color: 'neutral.100' },
50 | },
51 | '@link': {
52 | px: 0,
53 | backgroundColor: 'transparent',
54 | color: 'neutral.950',
55 | _dark: { color: 'neutral.100' },
56 | },
57 | },
58 | });
59 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-book-open-duotone.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconUnlock.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconUnlock = ficus(_Svg);
29 | export default IconUnlock;
30 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconTag.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconTag = ficus(_Svg);
29 | export default IconTag;
30 |
--------------------------------------------------------------------------------
/src/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
2 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3 | import { Slot } from 'expo-router';
4 | import * as SplashScreen from 'expo-splash-screen';
5 | import { FicusProvider } from 'react-native-ficus-ui';
6 | import { GestureHandlerRootView } from 'react-native-gesture-handler';
7 | import {
8 | initialWindowMetrics,
9 | SafeAreaProvider,
10 | } from 'react-native-safe-area-context';
11 | import 'react-native-reanimated';
12 | import '@/lib/i18n';
13 |
14 | import theme from '@/lib/ficus-ui/theme';
15 |
16 | import { ThemeManager } from '@/components/theme-manager';
17 | import { Sonner } from '@/components/ui/sonner';
18 |
19 | import { DevTools } from '@/features/devtools/devtools';
20 | import { SplashScreenManager } from '@/layout/splash-screen-manager';
21 |
22 | export const queryClient = new QueryClient();
23 |
24 | // SplashScreen hide management in on src/app/index.tsx
25 | SplashScreen.preventAutoHideAsync();
26 |
27 | export default function RootLayout() {
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {process.env.NODE_ENV === 'development' && }
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconHash.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
39 | );
40 | const IconHash = ficus(_Svg);
41 | export default IconHash;
42 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconLogOut.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconLogOut = ficus(_Svg);
35 | export default IconLogOut;
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconLanguages.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
19 | );
20 | const IconLanguages = ficus(_Svg);
21 | export default IconLanguages;
22 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconLogIn.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconLogIn = ficus(_Svg);
35 | export default IconLogIn;
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconShare.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconShare = ficus(_Svg);
35 | export default IconShare;
36 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconUpload.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconUpload = ficus(_Svg);
35 | export default IconUpload;
36 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-user-circle-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.rnstorybook/storybook.requires.ts:
--------------------------------------------------------------------------------
1 | /* do not change this file, it is auto generated by storybook. */
2 | import { start, updateView, View } from '@storybook/react-native';
3 |
4 | import '@storybook/addon-ondevice-controls/register';
5 | import '@storybook/addon-ondevice-actions/register';
6 |
7 | const normalizedStories = [
8 | {
9 | titlePrefix: '',
10 | directory: './.rnstorybook/stories',
11 | files: '**/*.stories.?(ts|tsx|js|jsx)',
12 | importPathMatcher:
13 | /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
14 | // @ts-ignore
15 | req: require.context(
16 | './stories',
17 | true,
18 | /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
19 | ),
20 | },
21 | {
22 | titlePrefix: '',
23 | directory: './src',
24 | files: '**/*.stories.@(js|jsx|mjs|ts|tsx)',
25 | importPathMatcher:
26 | /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(js|jsx|mjs|ts|tsx))$/,
27 | // @ts-ignore
28 | req: require.context(
29 | '../src',
30 | true,
31 | /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(js|jsx|mjs|ts|tsx))$/
32 | ),
33 | },
34 | ];
35 |
36 | declare global {
37 | var view: View;
38 | var STORIES: typeof normalizedStories;
39 | }
40 |
41 | const annotations = [require('@storybook/react-native/preview')];
42 |
43 | global.STORIES = normalizedStories;
44 |
45 | // @ts-ignore
46 | module?.hot?.accept?.();
47 |
48 | if (!global.view) {
49 | global.view = start({
50 | annotations,
51 | storyEntries: normalizedStories,
52 | });
53 | } else {
54 | updateView(global.view, annotations, normalizedStories);
55 | }
56 |
57 | export const view: View = global.view;
58 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconDownload.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
33 | );
34 | const IconDownload = ficus(_Svg);
35 | export default IconDownload;
36 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-sliders.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/app/(logged)/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack, useRouter } from 'expo-router';
2 | import { useEffect } from 'react';
3 | import { useTranslation } from 'react-i18next';
4 |
5 | import { useThemedStyle } from '@/hooks/use-themed-style';
6 |
7 | import { authClient } from '@/features/auth/client';
8 | import { ViewAuthOnboarding } from '@/features/auth/view-auth-onboarding';
9 |
10 | export default function LoggedLayout() {
11 | const router = useRouter();
12 | const session = authClient.useSession();
13 | const { t } = useTranslation(['layout']);
14 |
15 | useEffect(() => {
16 | if (!session.isPending && !session.data?.user) {
17 | router.replace('/(public)/sign-in');
18 | }
19 | }, [router, session.data?.user, session.isPending]);
20 |
21 | const themedStyle = useThemedStyle();
22 |
23 | if (!session.data?.user?.name) {
24 | return ;
25 | }
26 |
27 | return (
28 |
37 |
38 |
42 | {/* Add new logged-in View that's not included in tabs here */}
43 | ({
46 | headerShown: true,
47 | title: (props.route.params as { title?: string })?.title,
48 | })}
49 | />
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/icons/svg-sources/icon-key.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icons/generated/IconPower.tsx:
--------------------------------------------------------------------------------
1 | // Autogenerated file, DO NOT MODIFY or it will be overwritten;
2 | import * as React from 'react';
3 | import { ficus } from 'react-native-ficus-ui';
4 | import type { SvgProps } from 'react-native-svg';
5 | import Svg, { Path } from 'react-native-svg';
6 | const _Svg = (props: SvgProps) => (
7 |
27 | );
28 | const IconPower = ficus(_Svg);
29 | export default IconPower;
30 |
--------------------------------------------------------------------------------