create-croissant 0.1.43 → 0.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/add.js +25 -0
  2. package/dist/index.js +20 -7
  3. package/package.json +5 -4
  4. package/template/.expo/README.md +13 -0
  5. package/template/.expo/devices.json +3 -0
  6. package/template/apps/desktop/.eslintcache +1 -0
  7. package/template/apps/mobile/app/(tabs)/_layout.tsx +21 -14
  8. package/template/apps/mobile/app/(tabs)/account.tsx +147 -0
  9. package/template/apps/mobile/app/(tabs)/explore.tsx +334 -104
  10. package/template/apps/mobile/app/(tabs)/index.tsx +102 -85
  11. package/template/apps/mobile/app/_layout.tsx +26 -7
  12. package/template/apps/mobile/app/index.tsx +136 -0
  13. package/template/apps/mobile/app/login.tsx +135 -0
  14. package/template/apps/mobile/app/signup.tsx +144 -0
  15. package/template/apps/mobile/app.json +3 -3
  16. package/template/apps/mobile/components/ui/button.tsx +86 -0
  17. package/template/apps/mobile/components/ui/input.tsx +56 -0
  18. package/template/apps/mobile/lib/auth-client.ts +14 -0
  19. package/template/apps/mobile/lib/orpc.ts +23 -0
  20. package/template/apps/mobile/package.json +17 -2
  21. package/template/apps/mobile/tsconfig.json +4 -1
  22. package/template/apps/platform/package.json +2 -1
  23. package/template/apps/platform/src/components/login-form.tsx +5 -4
  24. package/template/apps/platform/src/components/signup-form.tsx +12 -16
  25. package/template/apps/platform/src/routeTree.gen.ts +267 -264
  26. package/template/apps/platform/src/routes/__root.tsx +6 -2
  27. package/template/apps/platform/src/routes/_auth/account.tsx +13 -17
  28. package/template/apps/platform/src/routes/_auth/examples/client-orpc-auth.tsx +2 -6
  29. package/template/apps/platform/src/routes/_public/examples/client-orpc.tsx +16 -29
  30. package/template/apps/platform/src/routes/_public/examples/ssr-orpc.tsx +10 -14
  31. package/template/apps/platform/src/routes/api/auth/$.ts +23 -2
  32. package/template/apps/platform/src/routes/api/rpc.$.ts +18 -0
  33. package/template/package.json +2 -4
  34. package/template/packages/orpc/package.json +7 -1
  35. package/template/packages/orpc/src/lib/planets.ts +18 -18
  36. package/template/packages/orpc/src/lib/router.ts +3 -3
  37. package/template/packages/orpc/src/react/context.tsx +23 -0
  38. package/template/packages/orpc/src/react/general.ts +29 -0
  39. package/template/packages/orpc/src/react/index.ts +3 -0
  40. package/template/packages/orpc/src/react/planets.ts +90 -0
  41. package/template/tsconfig.json +2 -1
  42. package/template/apps/mobile/app/modal.tsx +0 -29
package/dist/add.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import i from"path";import{fileURLToPath as E}from"url";import s from"chalk";import{Command as P}from"commander";import{execa as j}from"execa";import e from"fs-extra";import k from"inquirer";import g from"ora";var A=E(import.meta.url),x=i.dirname(A),y=new P;y.name("croissant-add").description("Add a mobile or desktop app to an existing Croissant Stack project").action(async()=>{console.log(s.bold.yellow(`
3
+ \u{1F950} Croissant Stack: Add App
4
+ `));let p=i.join(process.cwd(),"package.json");await e.pathExists(p)||(console.error(s.red(`
5
+ Error: package.json not found. Are you in the root of your project?
6
+ `)),process.exit(1));let t=await e.readJson(p);(!t.scripts||!t.scripts.dev&&!t.scripts.build)&&(console.error(s.red(`
7
+ Error: This doesn't look like a Croissant Stack project.
8
+ `)),process.exit(1));let u=i.join(process.cwd(),"apps/mobile"),h=i.join(process.cwd(),"apps/desktop"),f=await e.pathExists(u),b=await e.pathExists(h);if(f&&b){console.log(s.blue(`Both mobile and desktop apps are already present. Nothing to add!
9
+ `));return}let l=[];f||l.push({name:"Mobile App (Expo)",value:"mobile"}),b||l.push({name:"Desktop App (Electron)",value:"desktop"});let{type:a}=await k.prompt([{type:"list",name:"type",message:"Which app would you like to add?",choices:l}]),c=process.cwd(),w=g(`Adding ${a} app...`).start();try{let n=i.join(x,"..","template");if(await e.pathExists(n)||(n=i.resolve(x,"../../..")),a==="mobile"){let r=i.join(n,"apps/mobile");await e.copy(r,u),t.scripts=t.scripts||{},t.scripts["dev:mobile"]="turbo run dev --filter=mobile",t.scripts["build:mobile"]="turbo run build --filter=mobile",await e.writeJson(p,t,{spaces:2});let d=i.join(c,"packages/auth/src/lib/auth.ts");if(await e.pathExists(d)){let o=await e.readFile(d,"utf8");o.includes("@better-auth/expo")||(o=`import { expo } from "@better-auth/expo";
10
+ ${o}`),o.includes("plugins: [expo()]")||(o=o.replace("emailAndPassword: { enabled: true },",`emailAndPassword: { enabled: true },
11
+ plugins: [expo()],
12
+ trustedOrigins: [
13
+ "mobile://",
14
+ ...(process.env.NODE_ENV === "development"
15
+ ? [
16
+ "exp://",
17
+ "exp://**",
18
+ "exp://192.168.*.*:*/**",
19
+ "http://localhost:8081",
20
+ ]
21
+ : []),
22
+ ],`)),await e.writeFile(d,o)}let m=i.join(c,"packages/auth/package.json");if(await e.pathExists(m)){let o=await e.readJson(m);o.dependencies=o.dependencies||{},o.dependencies["@better-auth/expo"]||(o.dependencies["@better-auth/expo"]="latest",await e.writeJson(m,o,{spaces:2}))}}else if(a==="desktop"){let r=i.join(n,"apps/desktop");await e.copy(r,h),t.scripts=t.scripts||{},t.scripts["dev:desktop"]="turbo run dev --filter=desktop",t.scripts["build:desktop"]="turbo run build --filter=desktop",await e.writeJson(p,t,{spaces:2})}w.succeed(s.green(`${a==="mobile"?"Mobile":"Desktop"} app added successfully!`));let{install:v}=await k.prompt([{type:"confirm",name:"install",message:"Would you like to install dependencies now?",default:!0}]);if(v){let r=g("Installing dependencies...").start();try{await j("npm",["install"],{cwd:c}),r.succeed(s.green("Dependencies installed!"))}catch{r.fail(s.red("Failed to install dependencies. Run npm install manually."))}}console.log(s.bold.cyan(`
23
+ Next steps:`)),console.log(a==="mobile"?" npm run dev:mobile # Start Expo development server":" npm run dev:desktop # Start Electron development server"),console.log(s.yellow(`
24
+ Happy hacking! \u{1F950}
25
+ `))}catch(n){w.fail(s.red(`An error occurred while adding the ${a} app.`)),console.error(n),process.exit(1)}});y.parse(process.argv);
package/dist/index.js CHANGED
@@ -1,9 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import s from"path";import{fileURLToPath as k}from"url";import i from"chalk";import{Command as y}from"commander";import{execa as j}from"execa";import e from"fs-extra";import P from"inquirer";import u from"ora";var b=k(import.meta.url),g=s.dirname(b),h=new y;h.name("create-croissant").description("Scaffold a new project using the Croissant Stack").argument("[project-name]","Name of the project").action(async m=>{console.log(i.bold.yellow(`
2
+ import i from"path";import{fileURLToPath as x}from"url";import c from"chalk";import{Command as b}from"commander";import{execa as P}from"execa";import e from"fs-extra";import y from"inquirer";import g from"ora";var j=x(import.meta.url),w=i.dirname(j),k=new b;k.name("create-croissant").description("Scaffold a new project using the Croissant Stack").argument("[project-name]","Name of the project").action(async m=>{console.log(c.bold.yellow(`
3
3
  \u{1F950} Welcome to the Croissant Stack!
4
- `));let l=await P.prompt([{type:"input",name:"name",message:"What is your project named?",default:m||"my-croissant-app",when:!m},{type:"confirm",name:"install",message:"Would you like to install dependencies?",default:!0},{type:"confirm",name:"mobile",message:"Would you like to include the mobile app (Expo)?",default:!0},{type:"confirm",name:"desktop",message:"Would you like to include the desktop app (Electron)?",default:!0}]),c=m||l.name,o=s.resolve(process.cwd(),c);e.existsSync(o)&&(console.error(i.red(`
5
- Error: Directory ${c} already exists.
6
- `)),process.exit(1));let f=u("Scaffolding your project...").start();try{await e.ensureDir(o);let p=s.join(g,"..","template");if(await e.pathExists(p))await e.copy(p,o);else{let t=s.resolve(g,"../../.."),n=["node_modules","dist",".git","packages/create-croissant",".turbo","package-lock.json"];await e.copy(t,o,{filter:a=>{let r=s.relative(t,a);return!n.some(w=>r.startsWith(w))}})}if(!l.mobile){let t=s.join(o,"apps/mobile");if(await e.pathExists(t)){await e.remove(t);let n=s.join(o,"package.json");if(await e.pathExists(n)){let a=await e.readJson(n);a.scripts&&Object.keys(a.scripts).forEach(r=>{r.includes("mobile")&&delete a.scripts[r]}),await e.writeJson(n,a,{spaces:2})}}}if(!l.desktop){let t=s.join(o,"apps/desktop");if(await e.pathExists(t)){await e.remove(t);let n=s.join(o,"package.json");if(await e.pathExists(n)){let a=await e.readJson(n);a.scripts&&Object.keys(a.scripts).forEach(r=>{r.includes("desktop")&&delete a.scripts[r]}),await e.writeJson(n,a,{spaces:2})}}}let d=s.join(o,"package.json");if(await e.pathExists(d)){let t=await e.readJson(d);t.name=c,t.version="0.1.0",delete t.private,await e.writeJson(d,t,{spaces:2})}if(f.succeed(i.green(`Project ${c} created at ${o}`)),l.install){let t=u("Installing dependencies...").start();try{await j("npm",["install"],{cwd:o}),t.succeed(i.green("Dependencies installed!"))}catch{t.fail(i.red("Failed to install dependencies. You may need to run npm install manually."))}}console.log(i.bold.cyan(`
7
- Next steps:`)),console.log(` cd ${c}`),console.log(" npm run db:up # Start your PostgreSQL database"),console.log(` npm run dev # Start development server
8
- `),console.log(i.yellow(`Happy hacking! \u{1F950}
9
- `))}catch(p){f.fail(i.red("An error occurred during scaffolding.")),console.error(p),process.exit(1)}});h.parse(process.argv);
4
+ `));let l=await y.prompt([{type:"input",name:"name",message:"What is your project named?",default:m||"my-croissant-app",when:!m},{type:"confirm",name:"install",message:"Would you like to install dependencies?",default:!0},{type:"confirm",name:"mobile",message:"Would you like to include the mobile app (Expo)?",default:!0},{type:"confirm",name:"desktop",message:"Would you like to include the desktop app (Electron)?",default:!0}]),p=m||l.name,s=i.resolve(process.cwd(),p);e.existsSync(s)&&(console.error(c.red(`
5
+ Error: Directory ${p} already exists.
6
+ `)),process.exit(1));let h=g("Scaffolding your project...").start();try{await e.ensureDir(s);let d=i.join(w,"..","template");if(await e.pathExists(d))await e.copy(d,s);else{let a=i.resolve(w,"../../.."),n=["node_modules","dist",".git","packages/create-croissant",".turbo","package-lock.json"];await e.copy(a,s,{filter:t=>{let r=i.relative(a,t);return!n.some(o=>r.startsWith(o))}})}if(l.mobile){let a=i.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(a)){let t=await e.readFile(a,"utf8");t.includes("@better-auth/expo")||(t=`import { expo } from "@better-auth/expo";
7
+ ${t}`),t.includes("plugins: [expo()]")||(t=t.replace("emailAndPassword: { enabled: true },",`emailAndPassword: { enabled: true },
8
+ plugins: [expo()],
9
+ trustedOrigins: [
10
+ "mobile://",
11
+ ...(process.env.NODE_ENV === "development"
12
+ ? [
13
+ "exp://",
14
+ "exp://**",
15
+ "exp://192.168.*.*:*/**",
16
+ "http://localhost:8081",
17
+ ]
18
+ : []),
19
+ ],`)),await e.writeFile(a,t)}let n=i.join(s,"packages/auth/package.json");if(await e.pathExists(n)){let t=await e.readJson(n);t.dependencies=t.dependencies||{},t.dependencies["@better-auth/expo"]||(t.dependencies["@better-auth/expo"]="latest",await e.writeJson(n,t,{spaces:2}))}}else{let a=i.join(s,"apps/mobile");if(await e.pathExists(a)){await e.remove(a);let n=i.join(s,"package.json");if(await e.pathExists(n)){let o=await e.readJson(n);o.scripts&&Object.keys(o.scripts).forEach(f=>{f.includes("mobile")&&delete o.scripts[f]}),await e.writeJson(n,o,{spaces:2})}let t=i.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(t)){let o=await e.readFile(t,"utf8");o=o.replace(/import \{ expo \} from "@better-auth\/expo";\n/,""),o=o.replace(/\s+plugins: \[expo\(\)\],/,""),o=o.replace(/\s+trustedOrigins: \[[\s\S]*?\],/,""),await e.writeFile(t,o)}let r=i.join(s,"packages/auth/package.json");if(await e.pathExists(r)){let o=await e.readJson(r);o.dependencies&&o.dependencies["@better-auth/expo"]&&(delete o.dependencies["@better-auth/expo"],await e.writeJson(r,o,{spaces:2}))}}}if(!l.desktop){let a=i.join(s,"apps/desktop");if(await e.pathExists(a)){await e.remove(a);let n=i.join(s,"package.json");if(await e.pathExists(n)){let t=await e.readJson(n);t.scripts&&Object.keys(t.scripts).forEach(r=>{r.includes("desktop")&&delete t.scripts[r]}),await e.writeJson(n,t,{spaces:2})}}}let u=i.join(s,"package.json");if(await e.pathExists(u)){let a=await e.readJson(u);a.name=p,a.version="0.1.0",delete a.private,await e.writeJson(u,a,{spaces:2})}if(h.succeed(c.green(`Project ${p} created at ${s}`)),l.install){let a=g("Installing dependencies...").start();try{await P("npm",["install"],{cwd:s}),a.succeed(c.green("Dependencies installed!"))}catch{a.fail(c.red("Failed to install dependencies. You may need to run npm install manually."))}}console.log(c.bold.cyan(`
20
+ Next steps:`)),console.log(` cd ${p}`),console.log(" npm run db:up # Start your PostgreSQL database"),console.log(` npm run dev # Start development server
21
+ `),console.log(c.yellow(`Happy hacking! \u{1F950}
22
+ `))}catch(d){h.fail(c.red("An error occurred during scaffolding.")),console.error(d),process.exit(1)}});k.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-croissant",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "Scaffold a new project using the Croissant Stack",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,8 @@
8
8
  "directory": "packages/create-croissant"
9
9
  },
10
10
  "bin": {
11
- "create-croissant": "./dist/index.js"
11
+ "create-croissant": "./dist/index.js",
12
+ "croissant-add": "./dist/add.js"
12
13
  },
13
14
  "files": [
14
15
  "dist",
@@ -20,8 +21,8 @@
20
21
  "access": "public"
21
22
  },
22
23
  "scripts": {
23
- "build": "npm run prepare-template && tsup src/index.ts --format esm --clean --minify",
24
- "dev": "tsup src/index.ts --format esm --watch",
24
+ "build": "npm run prepare-template && tsup src/index.ts src/add.ts --format esm --clean --minify",
25
+ "dev": "tsup src/index.ts src/add.ts --format esm --watch",
25
26
  "typecheck": "tsc --noEmit",
26
27
  "prepare-template": "node scripts/prepare-template.js"
27
28
  },
@@ -0,0 +1,13 @@
1
+ > Why do I have a folder named ".expo" in my project?
2
+
3
+ The ".expo" folder is created when an Expo project is started using "expo start" command.
4
+
5
+ > What do the files contain?
6
+
7
+ - "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
8
+ - "settings.json": contains the server configuration that is used to serve the application manifest.
9
+
10
+ > Should I commit the ".expo" folder?
11
+
12
+ No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
13
+ Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
@@ -0,0 +1,3 @@
1
+ {
2
+ "devices": []
3
+ }
@@ -0,0 +1 @@
1
+ [{"/Users/sam/Dev/croissant-stack/apps/desktop/electron.vite.config.ts":"1","/Users/sam/Dev/croissant-stack/apps/desktop/src/main/index.ts":"2","/Users/sam/Dev/croissant-stack/apps/desktop/src/preload/index.d.ts":"3","/Users/sam/Dev/croissant-stack/apps/desktop/src/preload/index.ts":"4","/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/App.tsx":"5","/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/components/Versions.tsx":"6","/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/env.d.ts":"7","/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/main.tsx":"8","/Users/sam/Dev/croissant-stack/apps/desktop/out/main/index.js":"9","/Users/sam/Dev/croissant-stack/apps/desktop/out/preload/index.js":"10","/Users/sam/Dev/croissant-stack/apps/desktop/eslint.config.ts":"11","/Users/sam/Dev/croissant-stack/apps/desktop/prettier.config.ts":"12"},{"size":318,"mtime":1777164701886,"results":"13","hashOfConfig":"14"},{"size":2424,"mtime":1777164701886,"results":"15","hashOfConfig":"14"},{"size":149,"mtime":1777164701886,"results":"16","hashOfConfig":"14"},{"size":611,"mtime":1777163425865,"results":"17","hashOfConfig":"14"},{"size":1047,"mtime":1777163425865,"results":"18","hashOfConfig":"14"},{"size":428,"mtime":1777163425866,"results":"19","hashOfConfig":"14"},{"size":38,"mtime":1777163425866,"results":"20","hashOfConfig":"14"},{"size":232,"mtime":1777163425866,"results":"21","hashOfConfig":"14"},{"size":1579,"mtime":1777164321368,"results":"22","hashOfConfig":"23"},{"size":477,"mtime":1777164321388,"results":"24","hashOfConfig":"23"},{"size":239,"mtime":1777245470570,"results":"25","hashOfConfig":"14"},{"size":71,"mtime":1777165267271,"results":"26","hashOfConfig":"14"},{"filePath":"27","messages":"28","suppressedMessages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"19x1ah4",{"filePath":"30","messages":"31","suppressedMessages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","suppressedMessages":"35","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"36","messages":"37","suppressedMessages":"38","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","suppressedMessages":"41","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"42","messages":"43","suppressedMessages":"44","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"45","messages":"46","suppressedMessages":"47","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"48","messages":"49","suppressedMessages":"50","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"51","messages":"52","suppressedMessages":"53","errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"7zths6",{"filePath":"54","messages":"55","suppressedMessages":"56","errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"57","messages":"58","suppressedMessages":"59","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"60","messages":"61","suppressedMessages":"62","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/sam/Dev/croissant-stack/apps/desktop/electron.vite.config.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/main/index.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/preload/index.d.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/preload/index.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/App.tsx",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/components/Versions.tsx",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/env.d.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/src/renderer/src/main.tsx",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/out/main/index.js",["63"],[],"/Users/sam/Dev/croissant-stack/apps/desktop/out/preload/index.js",["64"],[],"/Users/sam/Dev/croissant-stack/apps/desktop/eslint.config.ts",[],[],"/Users/sam/Dev/croissant-stack/apps/desktop/prettier.config.ts",[],[],{"ruleId":null,"nodeType":null,"fatal":true,"severity":2,"message":"65"},{"ruleId":null,"nodeType":null,"fatal":true,"severity":2,"message":"66"},"Parsing error: /Users/sam/Dev/croissant-stack/apps/desktop/out/main/index.js was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.\nallowDefaultProject is set to [\"*.js\",\"*.mjs\"], which does not match 'out/main/index.js'.","Parsing error: /Users/sam/Dev/croissant-stack/apps/desktop/out/preload/index.js was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.\nallowDefaultProject is set to [\"*.js\",\"*.mjs\"], which does not match 'out/preload/index.js'."]
@@ -1,34 +1,41 @@
1
- import { Tabs } from "expo-router";
2
1
  import React from "react";
3
-
4
- import { HapticTab } from "@/components/haptic-tab";
5
- import { IconSymbol } from "@/components/ui/icon-symbol";
6
- import { Colors } from "@/constants/theme";
7
- import { useColorScheme } from "@/hooks/use-color-scheme";
2
+ import { Tabs } from "expo-router";
3
+ import { Ionicons } from "@expo/vector-icons";
8
4
 
9
5
  export default function TabLayout() {
10
- const colorScheme = useColorScheme();
11
-
12
6
  return (
13
7
  <Tabs
14
8
  screenOptions={{
15
- tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
16
- headerShown: false,
17
- tabBarButton: HapticTab,
9
+ tabBarActiveTintColor: "#000",
10
+ tabBarInactiveTintColor: "#888",
11
+ headerShown: true,
18
12
  }}
19
13
  >
20
14
  <Tabs.Screen
21
15
  name="index"
22
16
  options={{
23
- title: "Home",
24
- tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
17
+ title: "Dashboard",
18
+ tabBarIcon: ({ color, size }) => (
19
+ <Ionicons name="home" size={size} color={color} />
20
+ ),
25
21
  }}
26
22
  />
27
23
  <Tabs.Screen
28
24
  name="explore"
29
25
  options={{
30
26
  title: "Explore",
31
- tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
27
+ tabBarIcon: ({ color, size }) => (
28
+ <Ionicons name="planet" size={size} color={color} />
29
+ ),
30
+ }}
31
+ />
32
+ <Tabs.Screen
33
+ name="account"
34
+ options={{
35
+ title: "Account",
36
+ tabBarIcon: ({ color, size }) => (
37
+ <Ionicons name="person" size={size} color={color} />
38
+ ),
32
39
  }}
33
40
  />
34
41
  </Tabs>
@@ -0,0 +1,147 @@
1
+ import { useState, useEffect } from "react";
2
+ import { View, Text, StyleSheet, ScrollView, Alert, ActivityIndicator } from "react-native";
3
+ import { useRouter } from "expo-router";
4
+ import { authClient } from "@/lib/auth-client";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+
8
+ export default function AccountScreen() {
9
+ const router = useRouter();
10
+ const { data: session, isPending } = authClient.useSession();
11
+ const [updating, setUpdating] = useState(false);
12
+
13
+ // Form states
14
+ const [name, setName] = useState("");
15
+ const [email, setEmail] = useState("");
16
+
17
+ useEffect(() => {
18
+ if (!isPending && !session) {
19
+ router.replace("/login");
20
+ return;
21
+ }
22
+
23
+ if (session) {
24
+ setName(session.user.name);
25
+ setEmail(session.user.email);
26
+ }
27
+ }, [session, isPending, router]);
28
+
29
+ const handleUpdateProfile = async () => {
30
+ if (!name) {
31
+ Alert.alert("Error", "Name is required");
32
+ return;
33
+ }
34
+
35
+ setUpdating(true);
36
+ try {
37
+ const { error } = await authClient.updateUser({
38
+ name,
39
+ });
40
+
41
+ if (error) {
42
+ Alert.alert("Error", error.message || "Failed to update profile");
43
+ } else {
44
+ Alert.alert("Success", "Profile updated successfully");
45
+ }
46
+ } catch (err) {
47
+ Alert.alert("Error", "An unexpected error occurred");
48
+ } finally {
49
+ setUpdating(false);
50
+ }
51
+ };
52
+
53
+ if (isPending) {
54
+ return (
55
+ <View style={styles.center}>
56
+ <ActivityIndicator size="large" color="#000" />
57
+ </View>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <ScrollView style={styles.container} contentContainerStyle={styles.content}>
63
+ <Text style={styles.title}>Account Settings</Text>
64
+
65
+ <View style={styles.section}>
66
+ <Text style={styles.sectionTitle}>Profile Information</Text>
67
+ <View style={styles.form}>
68
+ <Input
69
+ label="Name"
70
+ value={name}
71
+ onChangeText={setName}
72
+ placeholder="Your name"
73
+ />
74
+ <Input
75
+ label="Email"
76
+ value={email}
77
+ editable={false}
78
+ style={styles.disabledInput}
79
+ />
80
+ <Button
81
+ onPress={handleUpdateProfile}
82
+ loading={updating}
83
+ style={styles.button}
84
+ >
85
+ Update Profile
86
+ </Button>
87
+ </View>
88
+ </View>
89
+
90
+ <View style={styles.section}>
91
+ <Text style={styles.sectionTitle}>Security</Text>
92
+ <Text style={styles.infoText}>
93
+ Password management and other security settings are currently available on the web platform.
94
+ </Text>
95
+ </View>
96
+ </ScrollView>
97
+ );
98
+ }
99
+
100
+ const styles = StyleSheet.create({
101
+ container: {
102
+ flex: 1,
103
+ backgroundColor: "#fff",
104
+ },
105
+ content: {
106
+ padding: 24,
107
+ },
108
+ center: {
109
+ flex: 1,
110
+ justifyContent: "center",
111
+ alignItems: "center",
112
+ },
113
+ title: {
114
+ fontSize: 28,
115
+ fontWeight: "bold",
116
+ marginBottom: 24,
117
+ },
118
+ section: {
119
+ marginBottom: 32,
120
+ },
121
+ sectionTitle: {
122
+ fontSize: 18,
123
+ fontWeight: "600",
124
+ marginBottom: 16,
125
+ color: "#333",
126
+ },
127
+ form: {
128
+ gap: 16,
129
+ },
130
+ disabledInput: {
131
+ backgroundColor: "#f5f5f5",
132
+ color: "#888",
133
+ },
134
+ button: {
135
+ marginTop: 8,
136
+ },
137
+ infoText: {
138
+ fontSize: 14,
139
+ color: "#666",
140
+ lineHeight: 20,
141
+ backgroundColor: "#f9f9f9",
142
+ padding: 16,
143
+ borderRadius: 8,
144
+ borderWidth: 1,
145
+ borderColor: "#eee",
146
+ },
147
+ });