Merge branch 'feat/icon-picker' into develop
This commit is contained in:
commit
97ae14d182
@ -1,5 +1,5 @@
|
||||
# Supabase - Local
|
||||
# EXPO_PUBLIC_SUPABASE_URL='http://127.0.0.1:54321' # Replace `127.0.0.1` with local IP (`hostname -I` on Linux)
|
||||
# EXPO_PUBLIC_SUPABASE_URL='http://127.0.0.1:54321' # Replace `127.0.0.1` with local IP (e.g: `hostname -i` on GNU/Linux)
|
||||
# EXPO_PUBLIC_SUPABASE_ANON_KEY=''
|
||||
|
||||
# Supabase - Production
|
||||
|
@ -65,7 +65,7 @@ npm run start
|
||||
Ce n'est pas strictement nécessaire pour le développement de l'application (même si recommandé), de lancer [Supabase](https://supabase.io/) en local, car l'application est déjà déployée sur un serveur [Supabase](https://supabase.io/) en production (`.env.example` est pré-configuré avec cet environnement).
|
||||
|
||||
```sh
|
||||
npm run supabase
|
||||
npm run supabase start
|
||||
```
|
||||
|
||||
#### Principales Commandes Supabase
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Stack } from "expo-router"
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
||||
import { library } from "@fortawesome/fontawesome-svg-core"
|
||||
import * as SplashScreen from "expo-splash-screen"
|
||||
import {
|
||||
MD3LightTheme as DefaultTheme,
|
||||
@ -20,6 +22,8 @@ export const unstableSettings = {
|
||||
initialRouteName: "index",
|
||||
}
|
||||
|
||||
library.add(fas)
|
||||
|
||||
SplashScreen.preventAutoHideAsync().catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Redirect, useLocalSearchParams } from "expo-router"
|
||||
|
||||
import { HabitEditForm } from "@/presentation/react/components/HabitEditForm/HabitEditForm"
|
||||
import { HabitEditForm } from "@/presentation/react/components/HabitForm/HabitEditForm"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
|
||||
const HabitPage: React.FC = () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HabitCreateForm } from "@/presentation/react/components/HabitCreateForm/HabitCreateForm"
|
||||
import { HabitCreateForm } from "@/presentation/react/components/HabitForm/HabitCreateForm"
|
||||
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
||||
|
||||
const NewHabitPage: React.FC = () => {
|
||||
|
@ -263,5 +263,5 @@ VALUES
|
||||
4733
|
||||
);
|
||||
|
||||
-- SELECT setval('habits_id_seq', (SELECT coalesce(MAX(id) + 1, 1) FROM habits), false);
|
||||
-- SELECT setval('habits_progresses_id_seq', (SELECT coalesce(MAX(id) + 1, 1) FROM habits_progresses), false);
|
||||
SELECT setval('habits_id_seq', (SELECT coalesce(MAX(id) + 1, 1) FROM habits), false);
|
||||
SELECT setval('habits_progresses_id_seq', (SELECT coalesce(MAX(id) + 1, 1) FROM habits_progresses), false);
|
||||
|
251
package-lock.json
generated
251
package-lock.json
generated
@ -10,10 +10,13 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "14.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@fortawesome/react-native-fontawesome": "0.3.0",
|
||||
"@hookform/resolvers": "3.3.4",
|
||||
"@react-native-async-storage/async-storage": "1.21.0",
|
||||
"@react-navigation/native": "6.1.16",
|
||||
"@supabase/supabase-js": "2.42.1",
|
||||
"@supabase/supabase-js": "2.42.3",
|
||||
"expo": "50.0.15",
|
||||
"expo-font": "11.10.3",
|
||||
"expo-linking": "6.2.2",
|
||||
@ -26,7 +29,7 @@
|
||||
"lottie-react-native": "6.5.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.51.2",
|
||||
"react-hook-form": "7.51.3",
|
||||
"react-native": "0.73.6",
|
||||
"react-native-calendars": "1.1304.1",
|
||||
"react-native-elements": "3.4.3",
|
||||
@ -50,7 +53,7 @@
|
||||
"@tsconfig/strictest": "2.0.5",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/react": "18.2.76",
|
||||
"@types/react": "18.2.77",
|
||||
"@types/react-test-renderer": "18.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.6.0",
|
||||
"@typescript-eslint/parser": "7.6.0",
|
||||
@ -4303,6 +4306,53 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
|
||||
"integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz",
|
||||
"integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
|
||||
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/react-native-fontawesome": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-native-fontawesome/-/react-native-fontawesome-0.3.0.tgz",
|
||||
"integrity": "sha512-wSfetdK4+b/pvPbM2v+bZ5hfNlwtk9l3QuJo59sbMrxJalfX7BuF2WsSIWMSxfWwSsbOtY4+TUs6uw/rE59NJA==",
|
||||
"dependencies": {
|
||||
"humps": "^2.0.1",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
||||
"react-native": ">= 0.67",
|
||||
"react-native-svg": ">= 11.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@ -7970,17 +8020,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/postgrest-js": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.1.tgz",
|
||||
"integrity": "sha512-H/4PABAAgMrEIo7oewUZiZMy422Pgc19OLVjU8Vwopcvfr3GD7h8Re4VXtiaPPZqc/2z/k3PnyguFnoKIB7fkA==",
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.2.tgz",
|
||||
"integrity": "sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==",
|
||||
"dependencies": {
|
||||
"@supabase/node-fetch": "^2.6.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/realtime-js": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz",
|
||||
"integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==",
|
||||
"version": "2.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.4.tgz",
|
||||
"integrity": "sha512-wdq+2hZpgw0r2ldRs87d3U08Y8BrsO1bZxPNqbImpYshAEkusDz4vufR8KaqujKxqewmXS6YnUhuRVdvSEIKCA==",
|
||||
"dependencies": {
|
||||
"@supabase/node-fetch": "^2.6.14",
|
||||
"@types/phoenix": "^1.5.4",
|
||||
@ -7997,15 +8047,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/supabase-js": {
|
||||
"version": "2.42.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.42.1.tgz",
|
||||
"integrity": "sha512-y05XIC6wChApz8BWE2TLUo6SBGp9ttbjhQ9DcQXI897vI3RRPIjm3wZqUoZiexUco+kgt3Em53+m55nB8Um6Sg==",
|
||||
"version": "2.42.3",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.42.3.tgz",
|
||||
"integrity": "sha512-/o52L/ngsGapcOUygWigvxzBo/bUVM4bubZMsUSZqZc+9sgjXZsgP/cwyggWUv3QOIqmbBrfSPgDLUh+Ofgi7Q==",
|
||||
"dependencies": {
|
||||
"@supabase/auth-js": "2.63.0",
|
||||
"@supabase/functions-js": "2.2.2",
|
||||
"@supabase/node-fetch": "2.6.15",
|
||||
"@supabase/postgrest-js": "1.15.1",
|
||||
"@supabase/realtime-js": "2.9.3",
|
||||
"@supabase/postgrest-js": "1.15.2",
|
||||
"@supabase/realtime-js": "2.9.4",
|
||||
"@supabase/storage-js": "2.5.5"
|
||||
}
|
||||
},
|
||||
@ -8199,9 +8249,9 @@
|
||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.76",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.76.tgz",
|
||||
"integrity": "sha512-T6z/v7YxpswDM61Vq5KoSPTJqCkroJfsDIsvXCr4+qOY6gik5Ju4w0jf67cpC5z7ydOnp/E0V0W08pDRy8u9Xw==",
|
||||
"version": "18.2.77",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.77.tgz",
|
||||
"integrity": "sha512-CUT9KUUF+HytDM7WiXKLF9qUSg4tGImwy4FXTlfEDPEkkNUzJ7rVFolYweJ9fS1ljoIaP7M7Rdjc5eUm/Yu5AA==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@ -9596,6 +9646,12 @@
|
||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
|
||||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/bplist-creator": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
||||
@ -9881,9 +9937,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001608",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz",
|
||||
"integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==",
|
||||
"version": "1.0.30001609",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz",
|
||||
"integrity": "sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -10623,6 +10679,56 @@
|
||||
"hyphenate-style-name": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.14",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cssom": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||
@ -11157,6 +11263,32 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||
@ -11170,6 +11302,35 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||
@ -11213,9 +11374,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.733",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.733.tgz",
|
||||
"integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ=="
|
||||
"version": "1.4.735",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.735.tgz",
|
||||
"integrity": "sha512-pkYpvwg8VyOTQAeBqZ7jsmpCjko1Qc6We1ZtZCjRyYbT5v4AIUKDy5cQTRotQlSSZmMr8jqpEt6JtOj5k7lR7A=="
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.13.1",
|
||||
@ -11255,7 +11416,6 @@
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
@ -13655,6 +13815,11 @@
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/humps": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
|
||||
"integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g=="
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "9.0.11",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
|
||||
@ -17970,6 +18135,12 @@
|
||||
"resolved": "https://registry.npmjs.org/md5hex/-/md5hex-1.0.0.tgz",
|
||||
"integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ=="
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
@ -19055,6 +19226,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/nullthrows": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||
@ -20146,9 +20329,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.51.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.2.tgz",
|
||||
"integrity": "sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==",
|
||||
"version": "7.51.3",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.3.tgz",
|
||||
"integrity": "sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==",
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
@ -20369,6 +20552,20 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-svg": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.1.0.tgz",
|
||||
"integrity": "sha512-p0Sx0EpQNk1nu6UcMEiB8K9P04n3J7s+pNYUwf1d/Yz+v4hk961VjuVqjyndgiEbHZyWiKWLZRVNuvLpwjPY2A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^1.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-swipe-gestures": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
|
@ -20,10 +20,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "14.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@fortawesome/react-native-fontawesome": "0.3.0",
|
||||
"@hookform/resolvers": "3.3.4",
|
||||
"@react-native-async-storage/async-storage": "1.21.0",
|
||||
"@react-navigation/native": "6.1.16",
|
||||
"@supabase/supabase-js": "2.42.1",
|
||||
"@supabase/supabase-js": "2.42.3",
|
||||
"expo": "50.0.15",
|
||||
"expo-font": "11.10.3",
|
||||
"expo-linking": "6.2.2",
|
||||
@ -36,7 +39,7 @@
|
||||
"lottie-react-native": "6.5.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.51.2",
|
||||
"react-hook-form": "7.51.3",
|
||||
"react-native": "0.73.6",
|
||||
"react-native-calendars": "1.1304.1",
|
||||
"react-native-elements": "3.4.3",
|
||||
@ -60,7 +63,7 @@
|
||||
"@tsconfig/strictest": "2.0.5",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/react": "18.2.76",
|
||||
"@types/react": "18.2.77",
|
||||
"@types/react-test-renderer": "18.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.6.0",
|
||||
"@typescript-eslint/parser": "7.6.0",
|
||||
|
@ -1,6 +1,9 @@
|
||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { ScrollView, StyleSheet } from "react-native"
|
||||
import { ScrollView, StyleSheet, View } from "react-native"
|
||||
import {
|
||||
Button,
|
||||
HelperText,
|
||||
@ -15,7 +18,6 @@ import ColorPicker, {
|
||||
Panel1,
|
||||
Preview,
|
||||
} from "reanimated-color-picker"
|
||||
import { useState } from "react"
|
||||
|
||||
import type { GoalFrequency, GoalType } from "@/domain/entities/Goal"
|
||||
import { GOAL_FREQUENCIES, GOAL_TYPES } from "@/domain/entities/Goal"
|
||||
@ -23,6 +25,8 @@ import type { HabitCreateData } from "@/domain/entities/Habit"
|
||||
import { HabitCreateSchema } from "@/domain/entities/Habit"
|
||||
import type { User } from "@/domain/entities/User"
|
||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
||||
import { useBoolean } from "../../hooks/useBoolean"
|
||||
import { IconSelectorModal } from "./IconSelectorModal"
|
||||
|
||||
export interface HabitCreateFormProps {
|
||||
user: User
|
||||
@ -43,7 +47,7 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
userId: user.id,
|
||||
name: "",
|
||||
color: "#006CFF",
|
||||
icon: "lightbulb",
|
||||
icon: "circle-question",
|
||||
goal: {
|
||||
frequency: "daily",
|
||||
target: {
|
||||
@ -55,6 +59,12 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
|
||||
const [isVisibleSnackbar, setIsVisibleSnackbar] = useState(false)
|
||||
|
||||
const {
|
||||
value: isModalIconSelectorVisible,
|
||||
setTrue: openModalIconSelector,
|
||||
setFalse: closeModalIconSelector,
|
||||
} = useBoolean()
|
||||
|
||||
const onDismissSnackbar = (): void => {
|
||||
setIsVisibleSnackbar(false)
|
||||
}
|
||||
@ -62,6 +72,7 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
const onSubmit = async (data: HabitCreateData): Promise<void> => {
|
||||
await habitsTrackerPresenter.habitCreate(data)
|
||||
setIsVisibleSnackbar(true)
|
||||
closeModalIconSelector()
|
||||
reset()
|
||||
}
|
||||
|
||||
@ -164,7 +175,27 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<>
|
||||
<Text style={[styles.spacing]}>Habit Type</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.spacing,
|
||||
{ justifyContent: "center", alignContent: "center" },
|
||||
]}
|
||||
>
|
||||
Habit Type
|
||||
{/* <Tooltip
|
||||
title="Routine habits are activities performed regularly, while Target habits involve setting specific objectives to be achieved through repeated actions."
|
||||
enterTouchDelay={50}
|
||||
leaveTouchDelay={25}
|
||||
>
|
||||
<IconButton
|
||||
icon="chat-question-outline"
|
||||
selected
|
||||
size={24}
|
||||
onPress={() => {}}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
</Tooltip> */}
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
style={[{ width: "90%" }]}
|
||||
onValueChange={onChange}
|
||||
@ -205,16 +236,30 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, onBlur, value } }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<TextInput
|
||||
placeholder="Icon"
|
||||
onBlur={onBlur}
|
||||
onChangeText={onChange}
|
||||
value={value}
|
||||
style={[styles.spacing, { width: "90%" }]}
|
||||
mode="outlined"
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
gap: 20,
|
||||
marginVertical: 30,
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon size={36} icon={value as IconName} />
|
||||
<Button mode="contained" onPress={openModalIconSelector}>
|
||||
Choose an icon
|
||||
</Button>
|
||||
|
||||
<IconSelectorModal
|
||||
key={isModalIconSelectorVisible ? "visible" : "hidden"}
|
||||
isVisible={isModalIconSelectorVisible}
|
||||
selectedIcon={value}
|
||||
handleCloseModal={closeModalIconSelector}
|
||||
onIconSelect={onChange}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
name="icon"
|
||||
@ -225,7 +270,7 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
loading={habitCreate.state === "loading"}
|
||||
disabled={habitCreate.state === "loading"}
|
||||
style={[styles.spacing, { width: "90%" }]}
|
||||
style={[styles.spacing, { width: "100%" }]}
|
||||
>
|
||||
Create your habit! 🚀
|
||||
</Button>
|
@ -1,7 +1,9 @@
|
||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { ScrollView, StyleSheet } from "react-native"
|
||||
import { ScrollView, StyleSheet, View } from "react-native"
|
||||
import { Button, HelperText, Snackbar, TextInput } from "react-native-paper"
|
||||
import { SafeAreaView } from "react-native-safe-area-context"
|
||||
import ColorPicker, {
|
||||
@ -13,6 +15,8 @@ import ColorPicker, {
|
||||
import type { Habit, HabitEditData } from "@/domain/entities/Habit"
|
||||
import { HabitEditSchema } from "@/domain/entities/Habit"
|
||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
||||
import { useBoolean } from "../../hooks/useBoolean"
|
||||
import { IconSelectorModal } from "./IconSelectorModal"
|
||||
|
||||
export interface HabitEditFormProps {
|
||||
habit: Habit
|
||||
@ -37,6 +41,12 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ habit }) => {
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
value: isModalIconSelectorVisible,
|
||||
setTrue: openModalIconSelector,
|
||||
setFalse: closeModalIconSelector,
|
||||
} = useBoolean()
|
||||
|
||||
const [isVisibleSnackbar, setIsVisibleSnackbar] = useState(false)
|
||||
|
||||
const onDismissSnackbar = (): void => {
|
||||
@ -110,16 +120,30 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ habit }) => {
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, onBlur, value } }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<TextInput
|
||||
placeholder="Icon"
|
||||
onBlur={onBlur}
|
||||
onChangeText={onChange}
|
||||
value={value}
|
||||
style={[styles.spacing, { width: "90%" }]}
|
||||
mode="outlined"
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
gap: 20,
|
||||
marginVertical: 30,
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon size={36} icon={value as IconName} />
|
||||
<Button mode="contained" onPress={openModalIconSelector}>
|
||||
Choose an icon
|
||||
</Button>
|
||||
|
||||
<IconSelectorModal
|
||||
key={isModalIconSelectorVisible ? "visible" : "hidden"}
|
||||
isVisible={isModalIconSelectorVisible}
|
||||
selectedIcon={value}
|
||||
handleCloseModal={closeModalIconSelector}
|
||||
onIconSelect={onChange}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
name="icon"
|
121
presentation/react/components/HabitForm/IconSelectorModal.tsx
Normal file
121
presentation/react/components/HabitForm/IconSelectorModal.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
||||
import { memo, useCallback, useEffect, useState, useTransition } from "react"
|
||||
import { Modal, ScrollView, View } from "react-native"
|
||||
import { Button, List, Text, TextInput } from "react-native-paper"
|
||||
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
|
||||
import { IconsList } from "./IconsList"
|
||||
|
||||
export interface IconSelectorModalProps {
|
||||
isVisible?: boolean
|
||||
selectedIcon?: string
|
||||
onIconSelect: (icon: string) => void
|
||||
handleCloseModal?: () => void
|
||||
}
|
||||
|
||||
interface SearchInputProps {
|
||||
searchText: string
|
||||
handleSearch: (text: string) => void
|
||||
}
|
||||
const SearchInputWithoutMemo: React.FC<SearchInputProps> = (props) => {
|
||||
const { searchText, handleSearch } = props
|
||||
return (
|
||||
<TextInput label="Search" value={searchText} onChangeText={handleSearch} />
|
||||
)
|
||||
}
|
||||
const SearchInput = memo(SearchInputWithoutMemo)
|
||||
|
||||
const iconNames = Object.keys(fas).map((key) => {
|
||||
return fas[key]?.iconName ?? key
|
||||
})
|
||||
|
||||
const findIconsInLibrary = (icon: string): string[] => {
|
||||
return iconNames
|
||||
.filter((name, index, self) => {
|
||||
return name.includes(icon) && self.indexOf(name) === index
|
||||
})
|
||||
.slice(0, 50)
|
||||
}
|
||||
|
||||
export const IconSelectorModal: React.FC<IconSelectorModalProps> = ({
|
||||
isVisible = false,
|
||||
selectedIcon,
|
||||
onIconSelect,
|
||||
handleCloseModal,
|
||||
}) => {
|
||||
const [possibleIcons, setPossibleIcons] = useState<string[]>([])
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const [searchText, setSearchText] = useState<string>("")
|
||||
const [_isPending, startTransition] = useTransition()
|
||||
|
||||
const handleSearch = useCallback((text: string): void => {
|
||||
setSearchText(text)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handlePossibleIcons = (): void => {
|
||||
startTransition(() => {
|
||||
setPossibleIcons(findIconsInLibrary(searchText))
|
||||
setIsLoading(false)
|
||||
})
|
||||
}
|
||||
const debounceHandleSearch = setTimeout(handlePossibleIcons, 400)
|
||||
|
||||
return () => {
|
||||
return clearTimeout(debounceHandleSearch)
|
||||
}
|
||||
}, [searchText])
|
||||
|
||||
const handleIconSelect = useCallback(
|
||||
(icon: string): void => {
|
||||
onIconSelect(icon)
|
||||
},
|
||||
[onIconSelect],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal animationType="fade" transparent visible={isVisible}>
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 5,
|
||||
width: "96%",
|
||||
height: "99%",
|
||||
backgroundColor: "white",
|
||||
borderColor: "black",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginVertical: 20,
|
||||
}}
|
||||
>
|
||||
<Text style={{ marginVertical: 8 }}>Selected Icon:</Text>
|
||||
<FontAwesomeIcon size={46} icon={selectedIcon as IconName} />
|
||||
</View>
|
||||
<SearchInput searchText={searchText} handleSearch={handleSearch} />
|
||||
<ScrollView>
|
||||
<List.Section title="Choose an icon:">
|
||||
<IconsList
|
||||
isLoading={isLoading}
|
||||
selectedIcon={selectedIcon}
|
||||
possibleIcons={possibleIcons}
|
||||
handleIconSelect={handleIconSelect}
|
||||
/>
|
||||
</List.Section>
|
||||
</ScrollView>
|
||||
<View style={{ marginVertical: 15 }}>
|
||||
<Button mode="contained" onPress={handleCloseModal}>
|
||||
Save
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
74
presentation/react/components/HabitForm/IconsList.tsx
Normal file
74
presentation/react/components/HabitForm/IconsList.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import React, { memo } from "react"
|
||||
import { View } from "react-native"
|
||||
import { ActivityIndicator, IconButton, Text } from "react-native-paper"
|
||||
|
||||
export interface IconsListProps {
|
||||
selectedIcon?: string
|
||||
possibleIcons: string[]
|
||||
isLoading?: boolean
|
||||
handleIconSelect: (icon: string) => void
|
||||
}
|
||||
|
||||
const IconsListWithoutMemo: React.FC<IconsListProps> = (props) => {
|
||||
const {
|
||||
selectedIcon,
|
||||
possibleIcons,
|
||||
isLoading = false,
|
||||
handleIconSelect,
|
||||
} = props
|
||||
|
||||
if (possibleIcons.length <= 0) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
marginTop: 20,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="large" />
|
||||
) : (
|
||||
<Text>No results found</Text>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: 15,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{possibleIcons.map((icon) => {
|
||||
return (
|
||||
<IconButton
|
||||
key={icon}
|
||||
containerColor="white"
|
||||
icon={({ size }) => {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={icon as IconName}
|
||||
size={size}
|
||||
color={selectedIcon === icon ? "blue" : "black"}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
size={30}
|
||||
onPress={() => {
|
||||
handleIconSelect(icon)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export const IconsList = memo(IconsListWithoutMemo)
|
@ -1,15 +1,16 @@
|
||||
import FontAwesome6 from "@expo/vector-icons/FontAwesome6"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { useRouter } from "expo-router"
|
||||
import { useState } from "react"
|
||||
import { View } from "react-native"
|
||||
import { Checkbox, List, Text } from "react-native-paper"
|
||||
import type LottieView from "lottie-react-native"
|
||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import type { GoalBoolean } from "@/domain/entities/Goal"
|
||||
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
|
||||
export interface HabitCardProps {
|
||||
habitHistory: HabitHistory
|
||||
@ -65,9 +66,9 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||
left={() => {
|
||||
return (
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
<FontAwesome6
|
||||
<FontAwesomeIcon
|
||||
size={24}
|
||||
name={habit.icon}
|
||||
icon={habit.icon as IconName}
|
||||
style={[
|
||||
{
|
||||
width: 30,
|
||||
|
Reference in New Issue
Block a user