@ereactthohir/cli 1.1.0 → 1.2.0

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.
@@ -54,7 +54,19 @@ async function create(name) {
54
54
  type: 'list',
55
55
  name: 'uiSystem',
56
56
  message: 'UI Style:',
57
- choices: ['Rice UI (default, official)']
57
+ choices: [
58
+ 'Rice UI (default, official)',
59
+ 'Tailwind CSS',
60
+ 'Bootstrap 5',
61
+ 'JokoUI',
62
+ 'DaisyUI',
63
+ 'Mantine',
64
+ 'Material UI',
65
+ 'Ant Design',
66
+ 'Chakra UI',
67
+ 'Shadcn/ui',
68
+ 'None (Custom CSS)'
69
+ ]
58
70
  },
59
71
  {
60
72
  type: 'list',
@@ -214,6 +226,11 @@ DB_DRIVER=${answers.database.includes('sawit') ? 'sawit' : answers.database.toLo
214
226
  "App\\Providers\\AuthServiceProvider"
215
227
  ]
216
228
  }, { spaces: 2 });
229
+ // Generate template structure based on selection
230
+ if (answers.template !== 'None (Blank)') {
231
+ spinner.text = `Generating ${answers.template} template...`;
232
+ await generateTemplateStructure(projectDir, answers.template, answers.uiSystem);
233
+ }
217
234
  // Install Semouth if selected
218
235
  if (answers.installSemouth) {
219
236
  spinner.text = 'Installing Semouth components...';
@@ -221,14 +238,25 @@ DB_DRIVER=${answers.database.includes('sawit') ? 'sawit' : answers.database.toLo
221
238
  }
222
239
  // Write package.json
223
240
  const dependencies = {
224
- "@ereactthohir/core": "^1.0.0",
225
- "@ereactthohir/rice-ui": "^1.0.0",
241
+ "@ereactthohir/core": "^1.2.0",
242
+ "@ereactthohir/rice-ui": "^1.2.0",
226
243
  "react": "^18.0.0",
227
244
  "react-dom": "^18.0.0"
228
245
  };
246
+ // Add UI System dependencies
247
+ const uiDependencies = getUIDependencies(answers.uiSystem);
248
+ Object.assign(dependencies, uiDependencies);
249
+ // Add Template-specific dependencies
250
+ const templateDependencies = getTemplateDependencies(answers.template);
251
+ Object.assign(dependencies, templateDependencies);
229
252
  // Add Semouth dependencies if installed
230
253
  if (answers.installSemouth) {
231
- dependencies["@ereactthohir/semouth"] = "^1.0.0";
254
+ dependencies["@ereactthohir/semouth"] = "^1.2.0";
255
+ }
256
+ // Add UI configuration file if not using default Rice UI
257
+ if (answers.uiSystem !== 'Rice UI (default, official)') {
258
+ spinner.text = 'Configuring UI system...';
259
+ await configureUISystem(projectDir, answers.uiSystem);
232
260
  }
233
261
  await fs_extra_1.default.writeJSON(path_1.default.join(projectDir, 'package.json'), {
234
262
  name,
@@ -240,10 +268,11 @@ DB_DRIVER=${answers.database.includes('sawit') ? 'sawit' : answers.database.toLo
240
268
  },
241
269
  dependencies,
242
270
  devDependencies: {
243
- "@ereactthohir/cli": "^1.0.0",
271
+ "@ereactthohir/cli": "^1.2.0",
244
272
  "typescript": "^5.0.0",
245
273
  "@types/react": "^18.0.0",
246
- "@types/react-dom": "^18.0.0"
274
+ "@types/react-dom": "^18.0.0",
275
+ ...getUIDevDependencies(answers.uiSystem)
247
276
  }
248
277
  }, { spaces: 2 });
249
278
  spinner.succeed(chalk_1.default.green('Project created successfully!'));
@@ -581,4 +610,1680 @@ const code = 2fa.generateCode(secret);
581
610
  For more information, refer to the [Security Guide](../../docs/SECURITY.md).
582
611
  `);
583
612
  }
613
+ /**
614
+ * Generate template structure based on template selection
615
+ */
616
+ async function generateTemplateStructure(projectDir, template, uiSystem) {
617
+ // Create template-specific directories
618
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/components'));
619
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/pages'));
620
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/stores'));
621
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/hooks'));
622
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/services'));
623
+ switch (template) {
624
+ case 'Mobile App Starter':
625
+ await generateMobileAppTemplate(projectDir, uiSystem);
626
+ break;
627
+ case 'Admin Dashboard':
628
+ await generateAdminDashboardTemplate(projectDir, uiSystem);
629
+ break;
630
+ case 'Authentication Starter':
631
+ await generateAuthenticationTemplate(projectDir, uiSystem);
632
+ break;
633
+ case 'Enterprise App Template':
634
+ await generateEnterpriseTemplate(projectDir, uiSystem);
635
+ break;
636
+ case 'SaaS Template':
637
+ await generateSaaSTemplate(projectDir, uiSystem);
638
+ break;
639
+ }
640
+ }
641
+ /**
642
+ * Generate Mobile App Starter template
643
+ */
644
+ async function generateMobileAppTemplate(projectDir, uiSystem) {
645
+ // Create API service
646
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/services/api.ts'), `import axios, { AxiosInstance } from 'axios';
647
+
648
+ class ApiService {
649
+ private api: AxiosInstance;
650
+
651
+ constructor() {
652
+ this.api = axios.create({
653
+ baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
654
+ timeout: 10000,
655
+ });
656
+
657
+ // Add request interceptor
658
+ this.api.interceptors.request.use((config) => {
659
+ const token = localStorage.getItem('auth_token');
660
+ if (token) {
661
+ config.headers.Authorization = \`Bearer \${token}\`;
662
+ }
663
+ return config;
664
+ });
665
+
666
+ // Add response interceptor
667
+ this.api.interceptors.response.use(
668
+ (response) => response,
669
+ (error) => {
670
+ if (error.response?.status === 401) {
671
+ localStorage.removeItem('auth_token');
672
+ window.location.href = '/login';
673
+ }
674
+ return Promise.reject(error);
675
+ }
676
+ );
677
+ }
678
+
679
+ async get(url: string, config?: any) {
680
+ return this.api.get(url, config);
681
+ }
682
+
683
+ async post(url: string, data?: any, config?: any) {
684
+ return this.api.post(url, data, config);
685
+ }
686
+
687
+ async put(url: string, data?: any, config?: any) {
688
+ return this.api.put(url, data, config);
689
+ }
690
+
691
+ async delete(url: string, config?: any) {
692
+ return this.api.delete(url, config);
693
+ }
694
+ }
695
+
696
+ export default new ApiService();
697
+ `);
698
+ // Create state management (Zustand)
699
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/stores/useAppStore.ts'), `import { create } from 'zustand';
700
+
701
+ interface AppState {
702
+ user: any | null;
703
+ isLoading: boolean;
704
+ notifications: any[];
705
+
706
+ setUser: (user: any) => void;
707
+ setLoading: (loading: boolean) => void;
708
+ addNotification: (notification: any) => void;
709
+ removeNotification: (id: string) => void;
710
+ logout: () => void;
711
+ }
712
+
713
+ export const useAppStore = create<AppState>((set) => ({
714
+ user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')!) : null,
715
+ isLoading: false,
716
+ notifications: [],
717
+
718
+ setUser: (user) => {
719
+ localStorage.setItem('user', JSON.stringify(user));
720
+ set({ user });
721
+ },
722
+
723
+ setLoading: (loading) => set({ isLoading: loading }),
724
+
725
+ addNotification: (notification) =>
726
+ set((state) => ({ notifications: [...state.notifications, notification] })),
727
+
728
+ removeNotification: (id) =>
729
+ set((state) => ({
730
+ notifications: state.notifications.filter((n) => n.id !== id),
731
+ })),
732
+
733
+ logout: () => {
734
+ localStorage.removeItem('auth_token');
735
+ localStorage.removeItem('user');
736
+ set({ user: null });
737
+ },
738
+ }));
739
+ `);
740
+ // Create Home page
741
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Home.tsx'), `import React, { useEffect, useState } from 'react';
742
+ import { useAppStore } from '../stores/useAppStore';
743
+ import apiService from '../services/api';
744
+
745
+ export default function Home() {
746
+ const { user, isLoading, setLoading } = useAppStore();
747
+ const [data, setData] = useState<any[]>([]);
748
+
749
+ useEffect(() => {
750
+ fetchData();
751
+ }, []);
752
+
753
+ const fetchData = async () => {
754
+ try {
755
+ setLoading(true);
756
+ const response = await apiService.get('/data');
757
+ setData(response.data);
758
+ } catch (error) {
759
+ console.error('Failed to fetch data:', error);
760
+ } finally {
761
+ setLoading(false);
762
+ }
763
+ };
764
+
765
+ return (
766
+ <div className="p-4">
767
+ <h1 className="text-2xl font-bold mb-4">Welcome, {user?.name || 'Guest'}</h1>
768
+ {isLoading && <p>Loading...</p>}
769
+ <div className="grid gap-4">
770
+ {data.map((item, idx) => (
771
+ <div key={idx} className="border rounded p-4">
772
+ <h2 className="font-semibold">{item.title}</h2>
773
+ <p>{item.description}</p>
774
+ </div>
775
+ ))}
776
+ </div>
777
+ </div>
778
+ );
779
+ }
780
+ `);
781
+ // Create routes
782
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'routes/mobile.ts'), `import { Route } from '@ereactthohir/core';
783
+
784
+ Route.screen('Home', () => import('../resources/pages/Home'));
785
+ Route.screen('Profile', () => import('../resources/pages/Profile'));
786
+ Route.screen('Settings', () => import('../resources/pages/Settings'));
787
+ `);
788
+ }
789
+ /**
790
+ * Generate Admin Dashboard template
791
+ */
792
+ async function generateAdminDashboardTemplate(projectDir, uiSystem) {
793
+ // Create API service
794
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/services/api.ts'), `import axios from 'axios';
795
+
796
+ const api = axios.create({
797
+ baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
798
+ });
799
+
800
+ api.interceptors.request.use((config) => {
801
+ const token = localStorage.getItem('auth_token');
802
+ if (token) config.headers.Authorization = \`Bearer \${token}\`;
803
+ return config;
804
+ });
805
+
806
+ export default api;
807
+ `);
808
+ // Create Admin Store
809
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/stores/useAdminStore.ts'), `import { create } from 'zustand';
810
+
811
+ interface AdminState {
812
+ activeMenu: string;
813
+ users: any[];
814
+ products: any[];
815
+ settings: any;
816
+ stats: any;
817
+
818
+ setActiveMenu: (menu: string) => void;
819
+ setUsers: (users: any[]) => void;
820
+ setProducts: (products: any[]) => void;
821
+ setSettings: (settings: any) => void;
822
+ setStats: (stats: any) => void;
823
+ }
824
+
825
+ export const useAdminStore = create<AdminState>((set) => ({
826
+ activeMenu: 'dashboard',
827
+ users: [],
828
+ products: [],
829
+ settings: {},
830
+ stats: {},
831
+
832
+ setActiveMenu: (menu) => set({ activeMenu: menu }),
833
+ setUsers: (users) => set({ users }),
834
+ setProducts: (products) => set({ products }),
835
+ setSettings: (settings) => set({ settings }),
836
+ setStats: (stats) => set({ stats }),
837
+ }));
838
+ `);
839
+ // Create Dashboard component
840
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/components/Dashboard.tsx'), `import React, { useEffect } from 'react';
841
+ import { useAdminStore } from '../stores/useAdminStore';
842
+ import apiService from '../services/api';
843
+
844
+ export default function Dashboard() {
845
+ const { stats, setStats } = useAdminStore();
846
+
847
+ useEffect(() => {
848
+ loadStats();
849
+ }, []);
850
+
851
+ const loadStats = async () => {
852
+ try {
853
+ const response = await apiService.get('/admin/stats');
854
+ setStats(response.data);
855
+ } catch (error) {
856
+ console.error('Failed to load stats:', error);
857
+ }
858
+ };
859
+
860
+ return (
861
+ <div className="p-6">
862
+ <h1 className="text-3xl font-bold mb-6">Dashboard</h1>
863
+
864
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
865
+ <StatCard title="Total Users" value={stats?.totalUsers || 0} />
866
+ <StatCard title="Total Products" value={stats?.totalProducts || 0} />
867
+ <StatCard title="Revenue" value={\`\$\${stats?.revenue || 0}\`} />
868
+ <StatCard title="Active Sessions" value={stats?.activeSessions || 0} />
869
+ </div>
870
+
871
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
872
+ <ChartSection title="Sales Overview" />
873
+ <ChartSection title="User Activity" />
874
+ </div>
875
+ </div>
876
+ );
877
+ }
878
+
879
+ function StatCard({ title, value }: { title: string; value: any }) {
880
+ return (
881
+ <div className="bg-white p-4 rounded-lg shadow">
882
+ <h3 className="text-gray-500 text-sm">{title}</h3>
883
+ <p className="text-2xl font-bold mt-2">{value}</p>
884
+ </div>
885
+ );
886
+ }
887
+
888
+ function ChartSection({ title }: { title: string }) {
889
+ return (
890
+ <div className="bg-white p-6 rounded-lg shadow">
891
+ <h2 className="text-lg font-semibold mb-4">{title}</h2>
892
+ <div className="h-64 bg-gray-100 rounded flex items-center justify-center">
893
+ <p className="text-gray-400">Chart placeholder</p>
894
+ </div>
895
+ </div>
896
+ );
897
+ }
898
+ `);
899
+ // Create Users management page
900
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Users.tsx'), `import React, { useEffect } from 'react';
901
+ import { useAdminStore } from '../stores/useAdminStore';
902
+ import apiService from '../services/api';
903
+
904
+ export default function Users() {
905
+ const { users, setUsers } = useAdminStore();
906
+
907
+ useEffect(() => {
908
+ loadUsers();
909
+ }, []);
910
+
911
+ const loadUsers = async () => {
912
+ try {
913
+ const response = await apiService.get('/admin/users');
914
+ setUsers(response.data);
915
+ } catch (error) {
916
+ console.error('Failed to load users:', error);
917
+ }
918
+ };
919
+
920
+ return (
921
+ <div className="p-6">
922
+ <div className="flex justify-between items-center mb-6">
923
+ <h1 className="text-3xl font-bold">Users Management</h1>
924
+ <button className="bg-blue-500 text-white px-4 py-2 rounded">Add User</button>
925
+ </div>
926
+
927
+ <div className="bg-white rounded-lg shadow overflow-hidden">
928
+ <table className="w-full">
929
+ <thead className="bg-gray-100">
930
+ <tr>
931
+ <th className="px-6 py-3 text-left">Name</th>
932
+ <th className="px-6 py-3 text-left">Email</th>
933
+ <th className="px-6 py-3 text-left">Role</th>
934
+ <th className="px-6 py-3 text-left">Status</th>
935
+ <th className="px-6 py-3 text-left">Actions</th>
936
+ </tr>
937
+ </thead>
938
+ <tbody>
939
+ {users.map((user) => (
940
+ <tr key={user.id} className="border-t hover:bg-gray-50">
941
+ <td className="px-6 py-3">{user.name}</td>
942
+ <td className="px-6 py-3">{user.email}</td>
943
+ <td className="px-6 py-3">{user.role}</td>
944
+ <td className="px-6 py-3">
945
+ <span className={user.active ? 'text-green-600' : 'text-red-600'}>
946
+ {user.active ? 'Active' : 'Inactive'}
947
+ </span>
948
+ </td>
949
+ <td className="px-6 py-3">
950
+ <button className="text-blue-500 hover:underline mr-3">Edit</button>
951
+ <button className="text-red-500 hover:underline">Delete</button>
952
+ </td>
953
+ </tr>
954
+ ))}
955
+ </tbody>
956
+ </table>
957
+ </div>
958
+ </div>
959
+ );
960
+ }
961
+ `);
962
+ // Create admin routes
963
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'routes/admin.ts'), `import { Route } from '@ereactthohir/core';
964
+
965
+ Route.middleware(['auth', 'admin']).group(() => {
966
+ Route.get('/dashboard', 'AdminController@dashboard').name('admin.dashboard');
967
+ Route.get('/users', 'AdminController@users').name('admin.users');
968
+ Route.get('/products', 'AdminController@products').name('admin.products');
969
+ Route.get('/settings', 'AdminController@settings').name('admin.settings');
970
+ });
971
+ `);
972
+ }
973
+ /**
974
+ * Generate Authentication Starter template
975
+ */
976
+ async function generateAuthenticationTemplate(projectDir, uiSystem) {
977
+ // Create Auth store
978
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/stores/useAuthStore.ts'), `import { create } from 'zustand';
979
+ import apiService from '../services/api';
980
+
981
+ interface AuthState {
982
+ user: any | null;
983
+ isAuthenticated: boolean;
984
+ isLoading: boolean;
985
+ error: string | null;
986
+
987
+ login: (email: string, password: string) => Promise<void>;
988
+ register: (data: any) => Promise<void>;
989
+ logout: () => Promise<void>;
990
+ refresh: () => Promise<void>;
991
+ setError: (error: string | null) => void;
992
+ }
993
+
994
+ export const useAuthStore = create<AuthState>((set) => ({
995
+ user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')!) : null,
996
+ isAuthenticated: !!localStorage.getItem('auth_token'),
997
+ isLoading: false,
998
+ error: null,
999
+
1000
+ login: async (email, password) => {
1001
+ set({ isLoading: true, error: null });
1002
+ try {
1003
+ const response = await apiService.post('/auth/login', { email, password });
1004
+ localStorage.setItem('auth_token', response.data.token);
1005
+ localStorage.setItem('user', JSON.stringify(response.data.user));
1006
+ set({ user: response.data.user, isAuthenticated: true });
1007
+ } catch (error: any) {
1008
+ set({ error: error.response?.data?.message || 'Login failed' });
1009
+ throw error;
1010
+ } finally {
1011
+ set({ isLoading: false });
1012
+ }
1013
+ },
1014
+
1015
+ register: async (data) => {
1016
+ set({ isLoading: true, error: null });
1017
+ try {
1018
+ const response = await apiService.post('/auth/register', data);
1019
+ localStorage.setItem('auth_token', response.data.token);
1020
+ localStorage.setItem('user', JSON.stringify(response.data.user));
1021
+ set({ user: response.data.user, isAuthenticated: true });
1022
+ } catch (error: any) {
1023
+ set({ error: error.response?.data?.message || 'Registration failed' });
1024
+ throw error;
1025
+ } finally {
1026
+ set({ isLoading: false });
1027
+ }
1028
+ },
1029
+
1030
+ logout: async () => {
1031
+ try {
1032
+ await apiService.post('/auth/logout');
1033
+ } finally {
1034
+ localStorage.removeItem('auth_token');
1035
+ localStorage.removeItem('user');
1036
+ set({ user: null, isAuthenticated: false });
1037
+ }
1038
+ },
1039
+
1040
+ refresh: async () => {
1041
+ try {
1042
+ const response = await apiService.post('/auth/refresh');
1043
+ localStorage.setItem('auth_token', response.data.token);
1044
+ localStorage.setItem('user', JSON.stringify(response.data.user));
1045
+ set({ user: response.data.user });
1046
+ } catch (error) {
1047
+ localStorage.removeItem('auth_token');
1048
+ set({ user: null, isAuthenticated: false });
1049
+ }
1050
+ },
1051
+
1052
+ setError: (error) => set({ error }),
1053
+ }));
1054
+ `);
1055
+ // Create API service
1056
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/services/api.ts'), `import axios from 'axios';
1057
+
1058
+ const api = axios.create({
1059
+ baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
1060
+ });
1061
+
1062
+ api.interceptors.request.use((config) => {
1063
+ const token = localStorage.getItem('auth_token');
1064
+ if (token) config.headers.Authorization = \`Bearer \${token}\`;
1065
+ return config;
1066
+ });
1067
+
1068
+ api.interceptors.response.use(
1069
+ (response) => response,
1070
+ async (error) => {
1071
+ if (error.response?.status === 401) {
1072
+ localStorage.removeItem('auth_token');
1073
+ window.location.href = '/login';
1074
+ }
1075
+ return Promise.reject(error);
1076
+ }
1077
+ );
1078
+
1079
+ export default api;
1080
+ `);
1081
+ // Create Login page
1082
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Login.tsx'), `import React, { useState } from 'react';
1083
+ import { useAuthStore } from '../stores/useAuthStore';
1084
+ import { useNavigate } from 'react-router-dom';
1085
+
1086
+ export default function Login() {
1087
+ const [email, setEmail] = useState('');
1088
+ const [password, setPassword] = useState('');
1089
+ const { login, isLoading, error } = useAuthStore();
1090
+ const navigate = useNavigate();
1091
+
1092
+ const handleSubmit = async (e: React.FormEvent) => {
1093
+ e.preventDefault();
1094
+ try {
1095
+ await login(email, password);
1096
+ navigate('/dashboard');
1097
+ } catch (error) {
1098
+ console.error('Login failed');
1099
+ }
1100
+ };
1101
+
1102
+ return (
1103
+ <div className="flex items-center justify-center min-h-screen bg-gray-100">
1104
+ <div className="w-full max-w-md bg-white rounded-lg shadow">
1105
+ <div className="p-8">
1106
+ <h1 className="text-2xl font-bold text-center mb-6">Login</h1>
1107
+
1108
+ {error && (
1109
+ <div className="mb-4 p-4 bg-red-100 text-red-700 rounded">
1110
+ {error}
1111
+ </div>
1112
+ )}
1113
+
1114
+ <form onSubmit={handleSubmit}>
1115
+ <div className="mb-4">
1116
+ <label className="block text-gray-700 text-sm font-semibold mb-2">
1117
+ Email
1118
+ </label>
1119
+ <input
1120
+ type="email"
1121
+ value={email}
1122
+ onChange={(e) => setEmail(e.target.value)}
1123
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1124
+ required
1125
+ />
1126
+ </div>
1127
+
1128
+ <div className="mb-6">
1129
+ <label className="block text-gray-700 text-sm font-semibold mb-2">
1130
+ Password
1131
+ </label>
1132
+ <input
1133
+ type="password"
1134
+ value={password}
1135
+ onChange={(e) => setPassword(e.target.value)}
1136
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1137
+ required
1138
+ />
1139
+ </div>
1140
+
1141
+ <button
1142
+ type="submit"
1143
+ disabled={isLoading}
1144
+ className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 disabled:bg-gray-400"
1145
+ >
1146
+ {isLoading ? 'Logging in...' : 'Login'}
1147
+ </button>
1148
+ </form>
1149
+
1150
+ <p className="text-center mt-4 text-gray-600">
1151
+ Don't have an account? <a href="/register" className="text-blue-500 hover:underline">Register</a>
1152
+ </p>
1153
+ </div>
1154
+ </div>
1155
+ </div>
1156
+ );
1157
+ }
1158
+ `);
1159
+ // Create Register page
1160
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Register.tsx'), `import React, { useState } from 'react';
1161
+ import { useAuthStore } from '../stores/useAuthStore';
1162
+ import { useNavigate } from 'react-router-dom';
1163
+
1164
+ export default function Register() {
1165
+ const [formData, setFormData] = useState({ name: '', email: '', password: '', password_confirmation: '' });
1166
+ const { register, isLoading, error } = useAuthStore();
1167
+ const navigate = useNavigate();
1168
+
1169
+ const handleSubmit = async (e: React.FormEvent) => {
1170
+ e.preventDefault();
1171
+ if (formData.password !== formData.password_confirmation) {
1172
+ alert('Passwords do not match');
1173
+ return;
1174
+ }
1175
+ try {
1176
+ await register(formData);
1177
+ navigate('/dashboard');
1178
+ } catch (error) {
1179
+ console.error('Registration failed');
1180
+ }
1181
+ };
1182
+
1183
+ return (
1184
+ <div className="flex items-center justify-center min-h-screen bg-gray-100">
1185
+ <div className="w-full max-w-md bg-white rounded-lg shadow">
1186
+ <div className="p-8">
1187
+ <h1 className="text-2xl font-bold text-center mb-6">Register</h1>
1188
+
1189
+ {error && (
1190
+ <div className="mb-4 p-4 bg-red-100 text-red-700 rounded">
1191
+ {error}
1192
+ </div>
1193
+ )}
1194
+
1195
+ <form onSubmit={handleSubmit}>
1196
+ <div className="mb-4">
1197
+ <label className="block text-gray-700 text-sm font-semibold mb-2">Name</label>
1198
+ <input
1199
+ type="text"
1200
+ value={formData.name}
1201
+ onChange={(e) => setFormData({...formData, name: e.target.value})}
1202
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1203
+ required
1204
+ />
1205
+ </div>
1206
+
1207
+ <div className="mb-4">
1208
+ <label className="block text-gray-700 text-sm font-semibold mb-2">Email</label>
1209
+ <input
1210
+ type="email"
1211
+ value={formData.email}
1212
+ onChange={(e) => setFormData({...formData, email: e.target.value})}
1213
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1214
+ required
1215
+ />
1216
+ </div>
1217
+
1218
+ <div className="mb-4">
1219
+ <label className="block text-gray-700 text-sm font-semibold mb-2">Password</label>
1220
+ <input
1221
+ type="password"
1222
+ value={formData.password}
1223
+ onChange={(e) => setFormData({...formData, password: e.target.value})}
1224
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1225
+ required
1226
+ />
1227
+ </div>
1228
+
1229
+ <div className="mb-6">
1230
+ <label className="block text-gray-700 text-sm font-semibold mb-2">Confirm Password</label>
1231
+ <input
1232
+ type="password"
1233
+ value={formData.password_confirmation}
1234
+ onChange={(e) => setFormData({...formData, password_confirmation: e.target.value})}
1235
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:border-blue-500"
1236
+ required
1237
+ />
1238
+ </div>
1239
+
1240
+ <button
1241
+ type="submit"
1242
+ disabled={isLoading}
1243
+ className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 disabled:bg-gray-400"
1244
+ >
1245
+ {isLoading ? 'Registering...' : 'Register'}
1246
+ </button>
1247
+ </form>
1248
+
1249
+ <p className="text-center mt-4 text-gray-600">
1250
+ Already have an account? <a href="/login" className="text-blue-500 hover:underline">Login</a>
1251
+ </p>
1252
+ </div>
1253
+ </div>
1254
+ </div>
1255
+ );
1256
+ }
1257
+ `);
1258
+ // Create auth routes
1259
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'routes/auth.ts'), `import { Route } from '@ereactthohir/core';
1260
+
1261
+ Route.get('/login', 'AuthController@showLogin').name('login');
1262
+ Route.post('/login', 'AuthController@login').name('login.store');
1263
+
1264
+ Route.get('/register', 'AuthController@showRegister').name('register');
1265
+ Route.post('/register', 'AuthController@register').name('register.store');
1266
+
1267
+ Route.middleware(['auth']).group(() => {
1268
+ Route.post('/logout', 'AuthController@logout').name('logout');
1269
+ });
1270
+ `);
1271
+ }
1272
+ /**
1273
+ * Generate Enterprise App Template
1274
+ */
1275
+ async function generateEnterpriseTemplate(projectDir, uiSystem) {
1276
+ // Create API service with advanced interceptors
1277
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/services/api.ts'), `import axios, { AxiosInstance } from 'axios';
1278
+
1279
+ class EnterpriseApiService {
1280
+ private api: AxiosInstance;
1281
+ private requestQueue: any[] = [];
1282
+ private isRefreshing = false;
1283
+
1284
+ constructor() {
1285
+ this.api = axios.create({
1286
+ baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
1287
+ timeout: 15000,
1288
+ });
1289
+
1290
+ this.setupInterceptors();
1291
+ }
1292
+
1293
+ private setupInterceptors() {
1294
+ this.api.interceptors.request.use((config) => {
1295
+ const token = localStorage.getItem('auth_token');
1296
+ if (token) {
1297
+ config.headers.Authorization = \`Bearer \${token}\`;
1298
+ }
1299
+ config.headers['X-Client-Version'] = '1.0.0';
1300
+ return config;
1301
+ });
1302
+
1303
+ this.api.interceptors.response.use(
1304
+ (response) => response,
1305
+ async (error) => {
1306
+ const originalRequest = error.config;
1307
+
1308
+ if (error.response?.status === 401 && !originalRequest._retry) {
1309
+ if (!this.isRefreshing) {
1310
+ this.isRefreshing = true;
1311
+ try {
1312
+ const response = await this.api.post('/auth/refresh');
1313
+ localStorage.setItem('auth_token', response.data.token);
1314
+ this.isRefreshing = false;
1315
+ this.processQueue(null);
1316
+
1317
+ originalRequest._retry = true;
1318
+ return this.api(originalRequest);
1319
+ } catch (refreshError) {
1320
+ this.isRefreshing = false;
1321
+ this.processQueue(refreshError);
1322
+ localStorage.removeItem('auth_token');
1323
+ window.location.href = '/login';
1324
+ }
1325
+ }
1326
+
1327
+ return new Promise((resolve, reject) => {
1328
+ this.requestQueue.push({ resolve, reject });
1329
+ }).then(() => this.api(originalRequest));
1330
+ }
1331
+
1332
+ return Promise.reject(error);
1333
+ }
1334
+ );
1335
+ }
1336
+
1337
+ private processQueue(error: any) {
1338
+ this.requestQueue.forEach((req) => {
1339
+ if (error) {
1340
+ req.reject(error);
1341
+ } else {
1342
+ req.resolve();
1343
+ }
1344
+ });
1345
+ this.requestQueue = [];
1346
+ }
1347
+
1348
+ async get(url: string, config?: any) { return this.api.get(url, config); }
1349
+ async post(url: string, data?: any, config?: any) { return this.api.post(url, data, config); }
1350
+ async put(url: string, data?: any, config?: any) { return this.api.put(url, data, config); }
1351
+ async patch(url: string, data?: any, config?: any) { return this.api.patch(url, data, config); }
1352
+ async delete(url: string, config?: any) { return this.api.delete(url, config); }
1353
+ }
1354
+
1355
+ export default new EnterpriseApiService();
1356
+ `);
1357
+ // Create advanced store with persistence
1358
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/stores/useEnterpriseStore.ts'), `import { create } from 'zustand';
1359
+ import { persist } from 'zustand/middleware';
1360
+
1361
+ interface User {
1362
+ id: number;
1363
+ name: string;
1364
+ email: string;
1365
+ role: string;
1366
+ permissions: string[];
1367
+ }
1368
+
1369
+ interface EnterpriseState {
1370
+ user: User | null;
1371
+ organization: any | null;
1372
+ features: string[];
1373
+ settings: Record<string, any>;
1374
+
1375
+ setUser: (user: User) => void;
1376
+ setOrganization: (org: any) => void;
1377
+ setFeatures: (features: string[]) => void;
1378
+ updateSettings: (settings: Record<string, any>) => void;
1379
+ hasPermission: (permission: string) => boolean;
1380
+ }
1381
+
1382
+ export const useEnterpriseStore = create<EnterpriseState>()(
1383
+ persist(
1384
+ (set, get) => ({
1385
+ user: null,
1386
+ organization: null,
1387
+ features: [],
1388
+ settings: {},
1389
+
1390
+ setUser: (user) => set({ user }),
1391
+ setOrganization: (organization) => set({ organization }),
1392
+ setFeatures: (features) => set({ features }),
1393
+ updateSettings: (settings) => set((state) => ({
1394
+ settings: { ...state.settings, ...settings }
1395
+ })),
1396
+ hasPermission: (permission: string) => {
1397
+ const { user } = get();
1398
+ return user?.permissions.includes(permission) || false;
1399
+ },
1400
+ }),
1401
+ { name: 'enterprise-store' }
1402
+ )
1403
+ );
1404
+ `);
1405
+ // Create modules structure
1406
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/modules'));
1407
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/modules/dashboard.ts'), `export const dashboardModule = {
1408
+ routes: [
1409
+ { path: '/dashboard', component: 'Dashboard', name: 'dashboard' },
1410
+ { path: '/analytics', component: 'Analytics', name: 'analytics' },
1411
+ ],
1412
+ permissions: ['view.dashboard', 'view.analytics'],
1413
+ };
1414
+ `);
1415
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/modules/users.ts'), `export const usersModule = {
1416
+ routes: [
1417
+ { path: '/users', component: 'UsersList', name: 'users.list' },
1418
+ { path: '/users/:id', component: 'UserDetail', name: 'users.show' },
1419
+ { path: '/users/create', component: 'UserForm', name: 'users.create' },
1420
+ ],
1421
+ permissions: ['manage.users'],
1422
+ };
1423
+ `);
1424
+ // Create advanced hooks
1425
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/hooks/useApi.ts'), `import { useState, useCallback } from 'react';
1426
+ import apiService from '../services/api';
1427
+
1428
+ export function useApi<T>(url: string, options?: any) {
1429
+ const [data, setData] = useState<T | null>(null);
1430
+ const [loading, setLoading] = useState(false);
1431
+ const [error, setError] = useState<any>(null);
1432
+
1433
+ const fetch = useCallback(async () => {
1434
+ setLoading(true);
1435
+ setError(null);
1436
+ try {
1437
+ const response = await apiService.get(url, options);
1438
+ setData(response.data);
1439
+ } catch (err) {
1440
+ setError(err);
1441
+ } finally {
1442
+ setLoading(false);
1443
+ }
1444
+ }, [url, options]);
1445
+
1446
+ return { data, loading, error, fetch };
1447
+ }
1448
+ `);
1449
+ // Create main routes
1450
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'routes/enterprise.ts'), `import { Route } from '@ereactthohir/core';
1451
+
1452
+ Route.middleware(['auth', 'verified']).group(() => {
1453
+ // Dashboard
1454
+ Route.get('/dashboard', 'DashboardController@index').name('dashboard');
1455
+
1456
+ // Users Management
1457
+ Route.get('/users', 'UserController@index').name('users.index');
1458
+ Route.get('/users/create', 'UserController@create').name('users.create');
1459
+ Route.post('/users', 'UserController@store').name('users.store');
1460
+ Route.get('/users/:id', 'UserController@show').name('users.show');
1461
+ Route.get('/users/:id/edit', 'UserController@edit').name('users.edit');
1462
+ Route.put('/users/:id', 'UserController@update').name('users.update');
1463
+ Route.delete('/users/:id', 'UserController@destroy').name('users.destroy');
1464
+
1465
+ // Settings
1466
+ Route.get('/settings', 'SettingsController@index').name('settings');
1467
+ Route.put('/settings', 'SettingsController@update').name('settings.update');
1468
+ });
1469
+ `);
1470
+ }
1471
+ /**
1472
+ * Generate SaaS Template
1473
+ */
1474
+ async function generateSaaSTemplate(projectDir, uiSystem) {
1475
+ // Create subscription service
1476
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/services/subscription.ts'), `import apiService from './api';
1477
+
1478
+ export class SubscriptionService {
1479
+ async getPlans() {
1480
+ const response = await apiService.get('/subscription/plans');
1481
+ return response.data;
1482
+ }
1483
+
1484
+ async subscribe(planId: number, billingCycle: 'monthly' | 'yearly') {
1485
+ const response = await apiService.post('/subscription/subscribe', {
1486
+ plan_id: planId,
1487
+ billing_cycle: billingCycle,
1488
+ });
1489
+ return response.data;
1490
+ }
1491
+
1492
+ async getCurrentSubscription() {
1493
+ const response = await apiService.get('/subscription/current');
1494
+ return response.data;
1495
+ }
1496
+
1497
+ async cancelSubscription() {
1498
+ const response = await apiService.post('/subscription/cancel');
1499
+ return response.data;
1500
+ }
1501
+
1502
+ async upgradeSubscription(planId: number) {
1503
+ const response = await apiService.post('/subscription/upgrade', { plan_id: planId });
1504
+ return response.data;
1505
+ }
1506
+
1507
+ async getInvoices() {
1508
+ const response = await apiService.get('/subscription/invoices');
1509
+ return response.data;
1510
+ }
1511
+ }
1512
+
1513
+ export default new SubscriptionService();
1514
+ `);
1515
+ // Create SaaS store
1516
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/stores/useSaaSStore.ts'), `import { create } from 'zustand';
1517
+ import subscriptionService from '../services/subscription';
1518
+
1519
+ interface Subscription {
1520
+ id: number;
1521
+ plan: any;
1522
+ status: 'active' | 'canceled' | 'expired';
1523
+ billing_cycle: 'monthly' | 'yearly';
1524
+ current_period_start: string;
1525
+ current_period_end: string;
1526
+ }
1527
+
1528
+ interface SaaSState {
1529
+ subscription: Subscription | null;
1530
+ plans: any[];
1531
+ invoices: any[];
1532
+ isLoading: boolean;
1533
+
1534
+ loadPlans: () => Promise<void>;
1535
+ loadSubscription: () => Promise<void>;
1536
+ loadInvoices: () => Promise<void>;
1537
+ subscribe: (planId: number, cycle: string) => Promise<void>;
1538
+ cancelSubscription: () => Promise<void>;
1539
+ }
1540
+
1541
+ export const useSaaSStore = create<SaaSState>((set) => ({
1542
+ subscription: null,
1543
+ plans: [],
1544
+ invoices: [],
1545
+ isLoading: false,
1546
+
1547
+ loadPlans: async () => {
1548
+ set({ isLoading: true });
1549
+ try {
1550
+ const plans = await subscriptionService.getPlans();
1551
+ set({ plans });
1552
+ } finally {
1553
+ set({ isLoading: false });
1554
+ }
1555
+ },
1556
+
1557
+ loadSubscription: async () => {
1558
+ try {
1559
+ const subscription = await subscriptionService.getCurrentSubscription();
1560
+ set({ subscription });
1561
+ } catch (error) {
1562
+ console.error('Failed to load subscription:', error);
1563
+ }
1564
+ },
1565
+
1566
+ loadInvoices: async () => {
1567
+ try {
1568
+ const invoices = await subscriptionService.getInvoices();
1569
+ set({ invoices });
1570
+ } catch (error) {
1571
+ console.error('Failed to load invoices:', error);
1572
+ }
1573
+ },
1574
+
1575
+ subscribe: async (planId: number, cycle: string) => {
1576
+ set({ isLoading: true });
1577
+ try {
1578
+ await subscriptionService.subscribe(planId, cycle as any);
1579
+ await subscriptionService.getCurrentSubscription();
1580
+ } finally {
1581
+ set({ isLoading: false });
1582
+ }
1583
+ },
1584
+
1585
+ cancelSubscription: async () => {
1586
+ set({ isLoading: true });
1587
+ try {
1588
+ await subscriptionService.cancelSubscription();
1589
+ set({ subscription: null });
1590
+ } finally {
1591
+ set({ isLoading: false });
1592
+ }
1593
+ },
1594
+ }));
1595
+ `);
1596
+ // Create Pricing page
1597
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Pricing.tsx'), `import React, { useEffect } from 'react';
1598
+ import { useSaaSStore } from '../stores/useSaaSStore';
1599
+
1600
+ export default function Pricing() {
1601
+ const { plans, loadPlans, isLoading } = useSaaSStore();
1602
+
1603
+ useEffect(() => {
1604
+ loadPlans();
1605
+ }, []);
1606
+
1607
+ return (
1608
+ <div className="py-12 px-4">
1609
+ <div className="text-center mb-12">
1610
+ <h1 className="text-4xl font-bold mb-4">Simple, Transparent Pricing</h1>
1611
+ <p className="text-gray-600">Choose the plan that fits your needs</p>
1612
+ </div>
1613
+
1614
+ {isLoading ? (
1615
+ <p className="text-center">Loading plans...</p>
1616
+ ) : (
1617
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
1618
+ {plans.map((plan) => (
1619
+ <PricingCard key={plan.id} plan={plan} />
1620
+ ))}
1621
+ </div>
1622
+ )}
1623
+ </div>
1624
+ );
1625
+ }
1626
+
1627
+ function PricingCard({ plan }: { plan: any }) {
1628
+ const { subscribe } = useSaaSStore();
1629
+
1630
+ return (
1631
+ <div className={'rounded-lg p-8 ' + (plan.featured ? 'bg-blue-50 border-2 border-blue-500' : 'bg-white border border-gray-200')}>
1632
+ <h3 className="text-xl font-bold mb-2">{plan.name}</h3>
1633
+ <p className="text-gray-600 text-sm mb-4">{plan.description}</p>
1634
+
1635
+ <div className="mb-6">
1636
+ <span className="text-3xl font-bold">\${plan.price}</span>
1637
+ <span className="text-gray-600">/month</span>
1638
+ </div>
1639
+
1640
+ <ul className="mb-6 space-y-2">
1641
+ {plan.features.map((feature: string) => (
1642
+ <li key={feature} className="flex items-center">
1643
+ <span className="mr-2">✓</span>
1644
+ <span>{feature}</span>
1645
+ </li>
1646
+ ))}
1647
+ </ul>
1648
+
1649
+ <button
1650
+ onClick={() => subscribe(plan.id, 'monthly')}
1651
+ className={plan.featured ? 'w-full bg-blue-500 text-white py-2 rounded' : 'w-full border border-gray-300 py-2 rounded'}
1652
+ >
1653
+ Choose Plan
1654
+ </button>
1655
+ </div>
1656
+ );
1657
+ }
1658
+ `);
1659
+ // Create Subscription Management page
1660
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/pages/Subscription.tsx'), `import React, { useEffect } from 'react';
1661
+ import { useSaaSStore } from '../stores/useSaaSStore';
1662
+
1663
+ export default function Subscription() {
1664
+ const { subscription, invoices, loadSubscription, loadInvoices } = useSaaSStore();
1665
+
1666
+ useEffect(() => {
1667
+ loadSubscription();
1668
+ loadInvoices();
1669
+ }, []);
1670
+
1671
+ return (
1672
+ <div className="p-6 max-w-4xl mx-auto">
1673
+ <h1 className="text-3xl font-bold mb-8">Subscription Management</h1>
1674
+
1675
+ {subscription && (
1676
+ <div className="bg-white rounded-lg shadow p-6 mb-8">
1677
+ <h2 className="text-xl font-semibold mb-4">Current Plan</h2>
1678
+ <div className="grid grid-cols-2 gap-4">
1679
+ <div>
1680
+ <p className="text-gray-600">Plan</p>
1681
+ <p className="font-semibold">{subscription.plan.name}</p>
1682
+ </div>
1683
+ <div>
1684
+ <p className="text-gray-600">Status</p>
1685
+ <p className="font-semibold text-green-600">{subscription.status}</p>
1686
+ </div>
1687
+ <div>
1688
+ <p className="text-gray-600">Renewal Date</p>
1689
+ <p className="font-semibold">{new Date(subscription.current_period_end).toLocaleDateString()}</p>
1690
+ </div>
1691
+ </div>
1692
+ </div>
1693
+ )}
1694
+
1695
+ <div className="bg-white rounded-lg shadow overflow-hidden">
1696
+ <div className="p-6 border-b">
1697
+ <h2 className="text-xl font-semibold">Invoices</h2>
1698
+ </div>
1699
+ <table className="w-full">
1700
+ <thead className="bg-gray-50">
1701
+ <tr>
1702
+ <th className="px-6 py-3 text-left">Date</th>
1703
+ <th className="px-6 py-3 text-left">Amount</th>
1704
+ <th className="px-6 py-3 text-left">Status</th>
1705
+ <th className="px-6 py-3 text-left">Action</th>
1706
+ </tr>
1707
+ </thead>
1708
+ <tbody>
1709
+ {invoices.map((invoice) => (
1710
+ <tr key={invoice.id} className="border-t">
1711
+ <td className="px-6 py-3">{new Date(invoice.date).toLocaleDateString()}</td>
1712
+ <td className="px-6 py-3">\${invoice.amount}</td>
1713
+ <td className="px-6 py-3">{invoice.status}</td>
1714
+ <td className="px-6 py-3">
1715
+ <a href={invoice.url} className="text-blue-500 hover:underline">Download</a>
1716
+ </td>
1717
+ </tr>
1718
+ ))}
1719
+ </tbody>
1720
+ </table>
1721
+ </div>
1722
+ </div>
1723
+ );
1724
+ }
1725
+ `);
1726
+ // Create SaaS routes
1727
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'routes/saas.ts'), `import { Route } from '@ereactthohir/core';
1728
+
1729
+ // Public pricing
1730
+ Route.get('/pricing', 'PricingController@index').name('pricing');
1731
+
1732
+ Route.middleware(['auth']).group(() => {
1733
+ // Subscription management
1734
+ Route.get('/subscription', 'SubscriptionController@index').name('subscription');
1735
+ Route.post('/subscription/subscribe', 'SubscriptionController@subscribe').name('subscription.subscribe');
1736
+ Route.post('/subscription/cancel', 'SubscriptionController@cancel').name('subscription.cancel');
1737
+ Route.post('/subscription/upgrade', 'SubscriptionController@upgrade').name('subscription.upgrade');
1738
+
1739
+ // Invoices
1740
+ Route.get('/invoices', 'InvoiceController@index').name('invoices');
1741
+ Route.get('/invoices/:id', 'InvoiceController@show').name('invoices.show');
1742
+ });
1743
+ `);
1744
+ }
1745
+ /**
1746
+ * Get UI Framework dependencies based on selection
1747
+ */
1748
+ function getUIDependencies(uiSystem) {
1749
+ const deps = {};
1750
+ switch (uiSystem) {
1751
+ case 'Tailwind CSS':
1752
+ deps['tailwindcss'] = '^3.3.0';
1753
+ break;
1754
+ case 'Bootstrap 5':
1755
+ deps['bootstrap'] = '^5.3.0';
1756
+ deps['react-bootstrap'] = '^2.8.0';
1757
+ break;
1758
+ case 'JokoUI':
1759
+ deps['jokoui'] = '^1.0.0';
1760
+ break;
1761
+ case 'DaisyUI':
1762
+ deps['daisyui'] = '^3.9.0';
1763
+ deps['tailwindcss'] = '^3.3.0';
1764
+ break;
1765
+ case 'Mantine':
1766
+ deps['@mantine/core'] = '^7.0.0';
1767
+ deps['@mantine/hooks'] = '^7.0.0';
1768
+ break;
1769
+ case 'Chakra UI':
1770
+ deps['@chakra-ui/react'] = '^2.8.0';
1771
+ deps['@emotion/react'] = '^11.11.0';
1772
+ deps['@emotion/styled'] = '^11.11.0';
1773
+ deps['framer-motion'] = '^10.12.0';
1774
+ break;
1775
+ case 'Shadcn/ui':
1776
+ deps['@radix-ui/primitives'] = '^1.0.0';
1777
+ deps['class-variance-authority'] = '^0.7.0';
1778
+ deps['clsx'] = '^2.0.0';
1779
+ deps['tailwindcss'] = '^3.3.0';
1780
+ break;
1781
+ case 'None (Custom CSS)':
1782
+ // No additional dependencies
1783
+ break;
1784
+ }
1785
+ return deps;
1786
+ }
1787
+ /**
1788
+ * Get UI Framework dev dependencies based on selection
1789
+ */
1790
+ function getUIDevDependencies(uiSystem) {
1791
+ const devDeps = {};
1792
+ switch (uiSystem) {
1793
+ case 'Tailwind CSS':
1794
+ case 'DaisyUI': // DaisyUI uses Tailwind
1795
+ devDeps['tailwindcss'] = '^3.3.0';
1796
+ devDeps['postcss'] = '^8.4.0';
1797
+ devDeps['autoprefixer'] = '^10.4.0';
1798
+ break;
1799
+ case 'Bootstrap 5':
1800
+ devDeps['sass'] = '^1.66.0';
1801
+ devDeps['sass-loader'] = '^13.3.0';
1802
+ break;
1803
+ case 'Shadcn/ui':
1804
+ devDeps['tailwindcss'] = '^3.3.0';
1805
+ devDeps['postcss'] = '^8.4.0';
1806
+ devDeps['autoprefixer'] = '^10.4.0';
1807
+ break;
1808
+ case 'Mantine':
1809
+ devDeps['postcss'] = '^8.4.0';
1810
+ devDeps['postcss-preset-mantine'] = '^1.0.0';
1811
+ devDeps['postcss-simple-vars'] = '^7.0.0';
1812
+ break;
1813
+ }
1814
+ return devDeps;
1815
+ }
1816
+ /**
1817
+ * Get Template-specific dependencies
1818
+ */
1819
+ function getTemplateDependencies(template) {
1820
+ const deps = {};
1821
+ // Common dependencies for all templates (except Blank)
1822
+ if (template !== 'None (Blank)') {
1823
+ deps['axios'] = '^1.4.0';
1824
+ deps['zustand'] = '^4.4.0';
1825
+ deps['react-router-dom'] = '^6.14.0';
1826
+ }
1827
+ switch (template) {
1828
+ case 'Mobile App Starter':
1829
+ deps['react-native'] = '^0.72.0';
1830
+ deps['react-native-navigation'] = '^7.0.0';
1831
+ break;
1832
+ case 'Admin Dashboard':
1833
+ deps['recharts'] = '^2.8.0';
1834
+ deps['lucide-react'] = '^0.263.0';
1835
+ break;
1836
+ case 'Enterprise App Template':
1837
+ deps['lodash'] = '^4.17.21';
1838
+ deps['date-fns'] = '^2.30.0';
1839
+ deps['react-hook-form'] = '^7.45.0';
1840
+ break;
1841
+ case 'SaaS Template':
1842
+ deps['stripe'] = '^12.10.0';
1843
+ deps['date-fns'] = '^2.30.0';
1844
+ deps['react-hook-form'] = '^7.45.0';
1845
+ break;
1846
+ case 'Authentication Starter':
1847
+ deps['jsonwebtoken'] = '^9.0.2';
1848
+ deps['bcryptjs'] = '^2.4.3';
1849
+ break;
1850
+ }
1851
+ return deps;
1852
+ }
1853
+ /**
1854
+ * Configure UI System files and configurations
1855
+ */
1856
+ async function configureUISystem(projectDir, uiSystem) {
1857
+ switch (uiSystem) {
1858
+ case 'Tailwind CSS':
1859
+ await configureTailwind(projectDir);
1860
+ break;
1861
+ case 'Bootstrap 5':
1862
+ await configureBootstrap(projectDir);
1863
+ break;
1864
+ case 'JokoUI':
1865
+ await configureJokoUI(projectDir);
1866
+ break;
1867
+ case 'DaisyUI':
1868
+ await configureDaisyUI(projectDir);
1869
+ break;
1870
+ case 'Mantine':
1871
+ await configureMantine(projectDir);
1872
+ break;
1873
+ case 'Material UI':
1874
+ await configureMaterialUI(projectDir);
1875
+ break;
1876
+ case 'Ant Design':
1877
+ await configureAntDesign(projectDir);
1878
+ break;
1879
+ case 'Chakra UI':
1880
+ await configureChakraUI(projectDir);
1881
+ break;
1882
+ case 'Shadcn/ui':
1883
+ await configureShadcn(projectDir);
1884
+ break;
1885
+ }
1886
+ }
1887
+ /**
1888
+ * Configure Tailwind CSS
1889
+ */
1890
+ async function configureTailwind(projectDir) {
1891
+ // Create tailwind.config.js
1892
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'tailwind.config.js'), `/** @type {import('tailwindcss').Config} */
1893
+ export default {
1894
+ content: [
1895
+ "./index.html",
1896
+ "./src/**/*.{js,ts,jsx,tsx}",
1897
+ "./resources/**/*.{js,ts,jsx,tsx}",
1898
+ ],
1899
+ theme: {
1900
+ extend: {},
1901
+ },
1902
+ plugins: [],
1903
+ }
1904
+ `);
1905
+ // Create postcss.config.js
1906
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'postcss.config.js'), `export default {
1907
+ plugins: {
1908
+ tailwindcss: {},
1909
+ autoprefixer: {},
1910
+ },
1911
+ }
1912
+ `);
1913
+ // Create tailwind.css
1914
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/css'));
1915
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/css/tailwind.css'), `@tailwind base;
1916
+ @tailwind components;
1917
+ @tailwind utilities;
1918
+ `);
1919
+ // Create example component with Tailwind
1920
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
1921
+
1922
+ export default function WelcomeComponent() {
1923
+ return (
1924
+ <div className="flex items-center justify-center min-h-screen bg-gradient-to-r from-blue-500 to-purple-600">
1925
+ <div className="text-center">
1926
+ <h1 className="text-5xl font-bold text-white mb-4">Welcome to EreactThohir</h1>
1927
+ <p className="text-xl text-gray-100 mb-8">With Tailwind CSS</p>
1928
+ <button className="px-8 py-3 bg-white text-blue-600 font-semibold rounded-lg hover:bg-gray-100 transition">
1929
+ Get Started
1930
+ </button>
1931
+ </div>
1932
+ </div>
1933
+ );
1934
+ }
1935
+ `);
1936
+ }
1937
+ /**
1938
+ * Configure Bootstrap 5
1939
+ */
1940
+ async function configureBootstrap(projectDir) {
1941
+ // Create example component with Bootstrap
1942
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
1943
+ import { Container, Row, Col, Button } from 'react-bootstrap';
1944
+ import 'bootstrap/dist/css/bootstrap.min.css';
1945
+
1946
+ export default function WelcomeComponent() {
1947
+ return (
1948
+ <Container className="d-flex align-items-center justify-content-center" style={{ minHeight: '100vh' }}>
1949
+ <Row className="text-center">
1950
+ <Col>
1951
+ <h1 className="display-4 fw-bold mb-4">Welcome to EreactThohir</h1>
1952
+ <p className="lead mb-4">With Bootstrap 5</p>
1953
+ <Button variant="primary" size="lg">Get Started</Button>
1954
+ </Col>
1955
+ </Row>
1956
+ </Container>
1957
+ );
1958
+ }
1959
+ `);
1960
+ }
1961
+ /**
1962
+ * Configure Material UI
1963
+ */
1964
+ async function configureMaterialUI(projectDir) {
1965
+ // Create example component with Material UI
1966
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
1967
+ import { Box, Container, Typography, Button, Stack } from '@mui/material';
1968
+
1969
+ export default function WelcomeComponent() {
1970
+ return (
1971
+ <Container maxWidth="md">
1972
+ <Box
1973
+ sx={{
1974
+ display: 'flex',
1975
+ alignItems: 'center',
1976
+ justifyContent: 'center',
1977
+ minHeight: '100vh',
1978
+ textAlign: 'center',
1979
+ }}
1980
+ >
1981
+ <Stack spacing={3}>
1982
+ <Typography variant="h1" component="h1">
1983
+ Welcome to EreactThohir
1984
+ </Typography>
1985
+ <Typography variant="h5" color="textSecondary">
1986
+ With Material UI
1987
+ </Typography>
1988
+ <Button variant="contained" size="large">
1989
+ Get Started
1990
+ </Button>
1991
+ </Stack>
1992
+ </Box>
1993
+ </Container>
1994
+ );
1995
+ }
1996
+ `);
1997
+ }
1998
+ /**
1999
+ * Configure Ant Design
2000
+ */
2001
+ async function configureAntDesign(projectDir) {
2002
+ // Create example component with Ant Design
2003
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2004
+ import { Layout, Button, Space, Typography } from 'antd';
2005
+ import 'antd/dist/reset.css';
2006
+
2007
+ const { Content } = Layout;
2008
+ const { Title, Text } = Typography;
2009
+
2010
+ export default function WelcomeComponent() {
2011
+ return (
2012
+ <Layout style={{ minHeight: '100vh' }}>
2013
+ <Content style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
2014
+ <Space direction="vertical" align="center" style={{ textAlign: 'center' }}>
2015
+ <Title level={1}>Welcome to EreactThohir</Title>
2016
+ <Text type="secondary" style={{ fontSize: '18px' }}>
2017
+ With Ant Design
2018
+ </Text>
2019
+ <Button type="primary" size="large">
2020
+ Get Started
2021
+ </Button>
2022
+ </Space>
2023
+ </Content>
2024
+ </Layout>
2025
+ );
2026
+ }
2027
+ `);
2028
+ }
2029
+ /**
2030
+ * Configure Chakra UI
2031
+ */
2032
+ async function configureChakraUI(projectDir) {
2033
+ // Create example component with Chakra UI
2034
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2035
+ import { Box, Container, Heading, Text, Button, VStack, Center } from '@chakra-ui/react';
2036
+
2037
+ export default function WelcomeComponent() {
2038
+ return (
2039
+ <Center minH="100vh">
2040
+ <Container maxW="md">
2041
+ <VStack spacing={8} textAlign="center">
2042
+ <Heading as="h1" size="2xl">
2043
+ Welcome to EreactThohir
2044
+ </Heading>
2045
+ <Text fontSize="lg" color="gray.600">
2046
+ With Chakra UI
2047
+ </Text>
2048
+ <Button colorScheme="blue" size="lg">
2049
+ Get Started
2050
+ </Button>
2051
+ </VStack>
2052
+ </Container>
2053
+ </Center>
2054
+ );
2055
+ }
2056
+ `);
2057
+ }
2058
+ /**
2059
+ * Configure Shadcn/ui
2060
+ */
2061
+ async function configureShadcn(projectDir) {
2062
+ // Create shadcn.json config
2063
+ await fs_extra_1.default.writeJSON(path_1.default.join(projectDir, 'components.json'), {
2064
+ "$schema": "https://ui.shadcn.com/schema.json",
2065
+ "style": "default",
2066
+ "rsc": true,
2067
+ "tsx": true,
2068
+ "tailwind": {
2069
+ "config": "tailwind.config.js",
2070
+ "css": "resources/css/globals.css",
2071
+ "baseColor": "slate"
2072
+ },
2073
+ "aliases": {
2074
+ "@/components": "./resources/components",
2075
+ "@/lib/utils": "./resources/lib/utils"
2076
+ }
2077
+ }, { spaces: 2 });
2078
+ // Create tailwind config for Shadcn
2079
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'tailwind.config.js'), `/** @type {import('tailwindcss').Config} */
2080
+ export default {
2081
+ content: [
2082
+ "./index.html",
2083
+ "./src/**/*.{js,ts,jsx,tsx}",
2084
+ "./resources/**/*.{js,ts,jsx,tsx}",
2085
+ ],
2086
+ theme: {
2087
+ extend: {
2088
+ colors: {
2089
+ border: "hsl(var(--border))",
2090
+ input: "hsl(var(--input))",
2091
+ ring: "hsl(var(--ring))",
2092
+ background: "hsl(var(--background))",
2093
+ foreground: "hsl(var(--foreground))",
2094
+ primary: {
2095
+ DEFAULT: "hsl(var(--primary))",
2096
+ foreground: "hsl(var(--primary-foreground))",
2097
+ },
2098
+ secondary: {
2099
+ DEFAULT: "hsl(var(--secondary))",
2100
+ foreground: "hsl(var(--secondary-foreground))",
2101
+ },
2102
+ },
2103
+ },
2104
+ },
2105
+ plugins: [require("tailwindcss-animate")],
2106
+ }
2107
+ `);
2108
+ // Create globals.css
2109
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/css'));
2110
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/css/globals.css'), `@tailwind base;
2111
+ @tailwind components;
2112
+ @tailwind utilities;
2113
+
2114
+ @layer base {
2115
+ :root {
2116
+ --background: 0 0% 100%;
2117
+ --foreground: 0 0% 3.6%;
2118
+ --card: 0 0% 100%;
2119
+ --card-foreground: 0 0% 3.6%;
2120
+ --popover: 0 0% 100%;
2121
+ --popover-foreground: 0 0% 3.6%;
2122
+ --muted: 0 0% 96.1%;
2123
+ --muted-foreground: 0 0% 45.1%;
2124
+ --accent: 0 0% 9.0%;
2125
+ --accent-foreground: 0 0% 100%;
2126
+ --destructive: 0 84.2% 60.2%;
2127
+ --destructive-foreground: 0 0% 100%;
2128
+ --border: 0 0% 89.8%;
2129
+ --input: 0 0% 89.8%;
2130
+ --primary: 0 0% 9.0%;
2131
+ --primary-foreground: 0 0% 100%;
2132
+ --secondary: 0 0% 96.1%;
2133
+ --secondary-foreground: 0 0% 9.0%;
2134
+ --ring: 0 0% 3.6%;
2135
+ }
2136
+ }
2137
+ `);
2138
+ // Create example component
2139
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2140
+ import { Button } from '@/components/ui/button';
2141
+
2142
+ export default function WelcomeComponent() {
2143
+ return (
2144
+ <div className="flex items-center justify-center min-h-screen">
2145
+ <div className="text-center">
2146
+ <h1 className="text-5xl font-bold mb-4">Welcome to EreactThohir</h1>
2147
+ <p className="text-xl text-muted-foreground mb-8">With Shadcn/ui</p>
2148
+ <Button size="lg">Get Started</Button>
2149
+ </div>
2150
+ </div>
2151
+ );
2152
+ }
2153
+ `);
2154
+ }
2155
+ /**
2156
+ * Configure JokoUI
2157
+ */
2158
+ async function configureJokoUI(projectDir) {
2159
+ // Create jokoui.config.js
2160
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'jokoui.config.js'), `export default {
2161
+ theme: {
2162
+ primary: '#3b82f6',
2163
+ secondary: '#10b981',
2164
+ },
2165
+ components: {
2166
+ button: {
2167
+ borderRadius: '0.5rem',
2168
+ }
2169
+ }
2170
+ }
2171
+ `);
2172
+ // Create example component
2173
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2174
+ import { Button, Card } from 'jokoui';
2175
+
2176
+ export default function WelcomeComponent() {
2177
+ return (
2178
+ <div style= {{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', backgroundColor: '#f3f4f6' }
2179
+ }>
2180
+ <Card style={ { padding: '2rem', textAlign: 'center', maxWidth: '500px' } }>
2181
+ <h1 style={ { fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '1rem' } }> Welcome to EreactThohir </h1>
2182
+ < p style = {{ color: '#666', marginBottom: '2rem' }
2183
+ }> With JokoUI </p>
2184
+ < Button variant = "primary" size = "lg" > Get Started </Button>
2185
+ </Card>
2186
+ </div>
2187
+ );
2188
+ }
2189
+ `);
2190
+ }
2191
+ /**
2192
+ * Configure DaisyUI
2193
+ */
2194
+ async function configureDaisyUI(projectDir) {
2195
+ // Configure Tailwind + DaisyUI
2196
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'tailwind.config.js'), `/** @type {import('tailwindcss').Config} */
2197
+ export default {
2198
+ content: [
2199
+ "./index.html",
2200
+ "./src/**/*.{js,ts,jsx,tsx}",
2201
+ "./resources/**/*.{js,ts,jsx,tsx}",
2202
+ ],
2203
+ theme: {
2204
+ extend: {},
2205
+ },
2206
+ plugins: [require("daisyui")],
2207
+ daisyui: {
2208
+ themes: ["light", "dark", "cupcake"],
2209
+ },
2210
+ }
2211
+ `);
2212
+ // Create postcss.config.js
2213
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'postcss.config.js'), `export default {
2214
+ plugins: {
2215
+ tailwindcss: {},
2216
+ autoprefixer: {},
2217
+ },
2218
+ }
2219
+ `);
2220
+ // Create tailwind.css
2221
+ await fs_extra_1.default.ensureDir(path_1.default.join(projectDir, 'resources/css'));
2222
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/css/tailwind.css'), `@tailwind base;
2223
+ @tailwind components;
2224
+ @tailwind utilities;
2225
+ `);
2226
+ // Create example component
2227
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2228
+
2229
+ export default function WelcomeComponent() {
2230
+ return (
2231
+ <div className= "hero min-h-screen bg-base-200" >
2232
+ <div className="hero-content text-center" >
2233
+ <div className="max-w-md" >
2234
+ <h1 className="text-5xl font-bold" > Welcome to EreactThohir </h1>
2235
+ < p className = "py-6" > With DaisyUI + Tailwind CSS </p>
2236
+ < button className = "btn btn-primary" > Get Started </button>
2237
+ < button className = "btn btn-secondary ml-2" > Documentation </button>
2238
+ </div>
2239
+ </div>
2240
+ </div>
2241
+ );
2242
+ }
2243
+ `);
2244
+ }
2245
+ /**
2246
+ * Configure Mantine
2247
+ */
2248
+ async function configureMantine(projectDir) {
2249
+ // Create postcss.config.js for Mantine
2250
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'postcss.config.cjs'), `module.exports = {
2251
+ plugins: {
2252
+ 'postcss-preset-mantine': {},
2253
+ 'postcss-simple-vars': {
2254
+ variables: {
2255
+ 'mantine-breakpoint-xs': '36em',
2256
+ 'mantine-breakpoint-sm': '48em',
2257
+ 'mantine-breakpoint-md': '62em',
2258
+ 'mantine-breakpoint-lg': '75em',
2259
+ 'mantine-breakpoint-xl': '88em',
2260
+ },
2261
+ },
2262
+ },
2263
+ };
2264
+ `);
2265
+ // Create Example Component
2266
+ await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'resources/views/WelcomeComponent.tsx'), `import React from 'react';
2267
+ import { Container, Title, Text, Button, Group } from '@mantine/core';
2268
+
2269
+ export default function WelcomeComponent() {
2270
+ return (
2271
+ <Container size= "md" h = "100vh" style = {{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }
2272
+ }>
2273
+ <Title order={ 1 } mb = "md" > Welcome to EreactThohir </Title>
2274
+ < Text size = "lg" c = "dimmed" mb = "xl" > With Mantine UI </Text>
2275
+
2276
+ < Group >
2277
+ <Button size="lg" variant = "filled" color = "blue" > Get Started </Button>
2278
+ < Button size = "lg" variant = "light" color = "blue" > Learn More </Button>
2279
+ </Group>
2280
+ </Container>
2281
+ );
2282
+ }
2283
+ `);
2284
+ // Note: User needs to wrap App with MantineProvider, but we can't easily edit App.tsx/index.tsx here without parsing it.
2285
+ // We assume the user or the template generator handles the provider wrapping if possible,
2286
+ // or we could overwrite the main entry point if we knew where it was.
2287
+ // For now, we'll just provide the component.
2288
+ }
584
2289
  //# sourceMappingURL=create.js.map