diff --git a/cli/commands/be.go b/cli/commands/be.go index 88a4c01b7..4a703a829 100644 --- a/cli/commands/be.go +++ b/cli/commands/be.go @@ -13,6 +13,7 @@ func RunBackendCommand() *cli.Command { command := cli.Command{ Name: "be", Usage: "Run the backend", + Category: "Development", Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) diff --git a/cli/commands/fe.go b/cli/commands/fe.go new file mode 100644 index 000000000..84a570ec6 --- /dev/null +++ b/cli/commands/fe.go @@ -0,0 +1,81 @@ +package commands + +import ( + "os" + "os/exec" + + _ "github.com/lib/pq" + "github.com/urfave/cli/v2" +) + +func RunFrontendCommand() *cli.Command { + command := cli.Command{ + Name: "fe", + Usage: "Run the frontend", + Category: "Development", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "target", + Aliases: []string{"t"}, + Value: "mobile", + Usage: "Run a specific frontend type (web or mobile)", + }, + &cli.StringFlag{ + Name: "platform", + Aliases: []string{"p"}, + Usage: "Run a specific platform for mobile frontend", + Value: "ios", + }, + }, + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + target := c.String("target") + if target != "web" && target != "mobile" { + return cli.Exit("Invalid frontend type: must be 'web' or 'mobile'", 1) + } + + + err := RunFE(c.String("type"), c.String("platform")) + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, + } + + return &command +} + +func RunFE(feType string, platform string) error { + switch feType { + case "mobile": + return RunMobileFE(platform) + case "web": + return RunWebFE() + default: + return RunMobileFE(platform) + } +} + +func RunMobileFE(platform string) error { + mobileCmd := exec.Command("yarn", "run", platform) + mobileCmd.Dir = FRONTEND_DIR + "/sac-mobile" + + mobileCmd.Stdout = os.Stdout + mobileCmd.Stderr = os.Stderr + mobileCmd.Stdin = os.Stdin + + if err := mobileCmd.Run(); err != nil { + return err + } + + return nil +} + +func RunWebFE() error { + return nil +} \ No newline at end of file diff --git a/cli/commands/format.go b/cli/commands/format.go index 9b60c9fc2..e1e37b1e0 100644 --- a/cli/commands/format.go +++ b/cli/commands/format.go @@ -2,79 +2,84 @@ package commands import ( "fmt" + "os" "os/exec" - "sync" "github.com/urfave/cli/v2" ) func FormatCommand() *cli.Command { - command := cli.Command{ - Name: "format", - Usage: "Runs formatting tools", - Aliases: []string{"f"}, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "frontend", + command := cli.Command{ + Name: "format", + Aliases: []string{"f"}, + Usage: "Runs formatting tools", + Category: "CI", + Subcommands: []*cli.Command{ + { + Name: "frontend", + Usage: "Format the frontend", Aliases: []string{"f"}, - Value: "", - Usage: "Formats a specific frontend folder", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "target", + Aliases: []string{"t"}, + Value: "mobile", + Usage: "Format a specific frontend type (web or mobile)", + }, + }, + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + target := c.String("target") + if target != "web" && target != "mobile" { + return cli.Exit("Invalid frontend type: must be 'web' or 'mobile'", 1) + } + + err := FormatFrontend(target) + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, }, - &cli.BoolFlag{ - Name: "backend", + { + Name: "backend", + Usage: "Format the backend", Aliases: []string{"b"}, - Usage: "Formats the backend", + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + err := FormatBackend() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, }, }, - Action: func(c *cli.Context) error { - if c.Args().Len() > 0 { - return cli.Exit("Invalid arguments", 1) - } - - if c.String("frontend") == "" && !c.Bool("backend") { - return cli.Exit("Must specify frontend or backend", 1) - } - - folder := c.String("frontend") - runFrontend := folder != "" - runBackend := c.Bool("backend") - - Format(folder, runFrontend, runBackend) - - return nil - }, } return &command } -func Format(folder string, runFrontend bool, runBackend bool) error { - var wg sync.WaitGroup - - // Start the backend if specified - if runBackend { - wg.Add(1) - go func() { - defer wg.Done() - BackendFormat() - }() - } - - // Start the frontend if specified - if runFrontend { - wg.Add(1) - go func() { - defer wg.Done() - FrontendFormat(folder) - }() +func FormatFrontend(target string) error { + switch target { + case "web": + return FormatWeb() + case "mobile": + return FormatMobile() + default: + return FormatMobile() } - - wg.Wait() - - return nil } -func BackendFormat() error { +func FormatBackend() error { fmt.Println("Formatting backend") cmd := exec.Command("gofumpt", "-l", "-w", ".") @@ -89,7 +94,21 @@ func BackendFormat() error { return nil } -func FrontendFormat(folder string) error { - fmt.Println("UNIMPLEMENTED") +func FormatWeb() error { return nil } + +func FormatMobile() error { + mobileCmd := exec.Command("yarn", "run", "format") + mobileCmd.Dir = FRONTEND_DIR + "/sac-mobile" + + mobileCmd.Stdout = os.Stdout + mobileCmd.Stderr = os.Stderr + mobileCmd.Stdin = os.Stdin + + if err := mobileCmd.Run(); err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/cli/commands/lint.go b/cli/commands/lint.go index 3351ab554..88c91811b 100644 --- a/cli/commands/lint.go +++ b/cli/commands/lint.go @@ -2,79 +2,91 @@ package commands import ( "fmt" + "os" "os/exec" - "sync" "github.com/urfave/cli/v2" ) func LintCommand() *cli.Command { - command := cli.Command{ - Name: "lint", - Aliases: []string{"l"}, - Usage: "Runs linting tools", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "frontend", + command := cli.Command{ + Name: "lint", + Aliases: []string{"l"}, + Usage: "Runs linting tools", + Category: "CI", + Subcommands: []*cli.Command{ + { + Name: "frontend", + Usage: "Lint the frontend", Aliases: []string{"f"}, - Value: "", - Usage: "Lint a specific frontend folder", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "target", + Aliases: []string{"t"}, + Value: "mobile", + Usage: "Lint a specific frontend type (web or mobile)", + }, + &cli.BoolFlag{ + Name: "fix", + Aliases: []string{"f"}, + Usage: "Fix linting errors", + }, + }, + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + target := c.String("target") + if target != "web" && target != "mobile" { + return cli.Exit("Invalid frontend type: must be 'web' or 'mobile'", 1) + } + + fix := c.Bool("fix") + + err := LintFrontend(target, fix) + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, }, - &cli.BoolFlag{ - Name: "backend", + { + Name: "backend", + Usage: "Lint the backend", Aliases: []string{"b"}, - Usage: "Lint the backend", + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + err := LintBackend() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, }, }, - Action: func(c *cli.Context) error { - if c.Args().Len() > 0 { - return cli.Exit("Invalid arguments", 1) - } - - if c.String("frontend") == "" && !c.Bool("backend") { - return cli.Exit("Must specify frontend or backend", 1) - } - - folder := c.String("frontend") - runFrontend := folder != "" - runBackend := c.Bool("backend") - - Lint(folder, runFrontend, runBackend) - - return nil - }, } return &command } -func Lint(folder string, runFrontend bool, runBackend bool) error { - var wg sync.WaitGroup - - // Start the backend if specified - if runBackend { - wg.Add(1) - go func() { - defer wg.Done() - BackendLint() - }() - } - - // Start the frontend if specified - if runFrontend { - wg.Add(1) - go func() { - defer wg.Done() - FrontendLint(folder) - }() +func LintFrontend(target string, fix bool) error { + switch target { + case "web": + return LintWeb(fix) + case "mobile": + return LintMobile(fix) + default: + return LintMobile(fix) } - - wg.Wait() - - return nil } -func BackendLint() error { +func LintBackend() error { fmt.Println("Linting backend") cmd := exec.Command("go", "vet", "./...") @@ -90,7 +102,26 @@ func BackendLint() error { return nil } -func FrontendLint(folder string) error { - fmt.Println("UNIMPLEMENTED") +func LintWeb(fix bool) error { return nil } + +func LintMobile(fix bool) error { + var mobileCmd *exec.Cmd + if fix { + mobileCmd = exec.Command("yarn", "run", "lint", "--fix") + } else { + mobileCmd = exec.Command("yarn", "run", "lint") + } + mobileCmd.Dir = FRONTEND_DIR + "/sac-mobile" + + mobileCmd.Stdout = os.Stdout + mobileCmd.Stderr = os.Stderr + mobileCmd.Stdin = os.Stdin + + if err := mobileCmd.Run(); err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/cli/commands/migrate.go b/cli/commands/migrate.go index a2189e805..08e432108 100644 --- a/cli/commands/migrate.go +++ b/cli/commands/migrate.go @@ -10,6 +10,7 @@ import ( func MigrateCommand() *cli.Command { command := cli.Command{ Name: "migrate", + Aliases: []string{"m"}, Usage: "Migrate the database, creating tables and relationships", Category: "Database Operations", Action: func(c *cli.Context) error { diff --git a/cli/main.go b/cli/main.go index 7ed2f9a0b..14e0b5005 100755 --- a/cli/main.go +++ b/cli/main.go @@ -19,12 +19,11 @@ func main() { commands.ResetCommand(), commands.InsertCommand(), commands.DropCommand(), - commands.ResetCommand(), - commands.DropCommand(), commands.RunBackendCommand(), + commands.RunFrontendCommand(), commands.TestCommand(), // TODO: frontend tests - commands.FormatCommand(), // TODO: frontend format - commands.LintCommand(), // TODO: frontend lint + commands.FormatCommand(), + commands.LintCommand(), }, } err := app.Run(os.Args) diff --git a/frontend/sac-mobile/.eslintrc.js b/frontend/sac-mobile/.eslintrc.js index 288af8545..2441767bf 100644 --- a/frontend/sac-mobile/.eslintrc.js +++ b/frontend/sac-mobile/.eslintrc.js @@ -1,17 +1,17 @@ module.exports = { - root: true, - extends: ['@react-native-community', 'plugin:prettier/recommended'], - plugins: ['prettier', 'jest'], - rules: { - 'prettier/prettier': 'error', - 'react/react-in-jsx-scope': 'off' - }, - env: { - 'jest/globals': true - }, - parserOptions: { - babelOptions: { - configFile: './babel.config.js' + root: true, + extends: ['@react-native-community', 'plugin:prettier/recommended'], + plugins: ['prettier', 'jest'], + rules: { + 'prettier/prettier': 'error', + 'react/react-in-jsx-scope': 'off' + }, + env: { + 'jest/globals': true + }, + parserOptions: { + babelOptions: { + configFile: './babel.config.js' + } } - } }; diff --git a/frontend/sac-mobile/.prettierrc b/frontend/sac-mobile/.prettierrc index 0c8098f06..182859683 100644 --- a/frontend/sac-mobile/.prettierrc +++ b/frontend/sac-mobile/.prettierrc @@ -1,21 +1,24 @@ { - "plugins": ["@trivago/prettier-plugin-sort-imports"], - "printWidth": 80, - "tabWidth": 2, - "semi": true, - "singleQuote": true, - "trailingComma": "none", - "bracketSpacing": true, - "arrowParens": "always", - "importOrder": [ - "^@core/(.*)$", - "^@server/(.*)$", - "^@ui/(.*)$", - "^lodash", - "^react", - "^rxjs", - "^[./]" - ], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true + "plugins": [ + "@trivago/prettier-plugin-sort-imports" + ], + "printWidth": 80, + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "auto", + "importOrder": [ + "^react", + "^react-native", + "^expo", + "", + "^@/", + "^src", + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true } diff --git a/frontend/sac-mobile/app.d.ts b/frontend/sac-mobile/app.d.ts index fbca8c7ea..a13e3136b 100644 --- a/frontend/sac-mobile/app.d.ts +++ b/frontend/sac-mobile/app.d.ts @@ -1 +1 @@ -/// \ No newline at end of file +/// diff --git a/frontend/sac-mobile/app.json b/frontend/sac-mobile/app.json index e79307e66..ff0952b83 100644 --- a/frontend/sac-mobile/app.json +++ b/frontend/sac-mobile/app.json @@ -1,51 +1,47 @@ { - "expo": { - "name": "sac-mobile", - "slug": "student-activity-calendar", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/images/icon.png", - "scheme": "myapp", - "userInterfaceStyle": "automatic", - "splash": { - "image": "./assets/images/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "assetBundlePatterns": [ - "**/*" - ], - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.generatesac.studentactivitycalendar" - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/images/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.generatesac.studentactivitycalendar" - }, - "web": { - "bundler": "metro", - "output": "static", - "favicon": "./assets/images/favicon.png" - }, - "plugins": [ - "expo-router" - ], - "experiments": { - "typedRoutes": true, - "tsconfigPaths": true - }, - "extra": { - "router": { - "origin": false - }, - "eas": { - "projectId": "9cfc60bc-2788-4837-9895-3fe0afdc13c5" - } - }, - "owner": "generatesac" - } + "expo": { + "name": "sac-mobile", + "slug": "student-activity-calendar", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "myapp", + "userInterfaceStyle": "automatic", + "splash": { + "image": "./assets/images/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": true, + "bundleIdentifier": "com.generatesac.studentactivitycalendar" + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/images/adaptive-icon.png", + "backgroundColor": "#ffffff" + }, + "package": "com.generatesac.studentactivitycalendar" + }, + "web": { + "bundler": "metro", + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": ["expo-router"], + "experiments": { + "typedRoutes": true, + "tsconfigPaths": true + }, + "extra": { + "router": { + "origin": false + }, + "eas": { + "projectId": "9cfc60bc-2788-4837-9895-3fe0afdc13c5" + } + }, + "owner": "generatesac" + } } diff --git a/frontend/sac-mobile/app/(app)/_layout.tsx b/frontend/sac-mobile/app/(app)/_layout.tsx index c47e972e8..579d41fe5 100644 --- a/frontend/sac-mobile/app/(app)/_layout.tsx +++ b/frontend/sac-mobile/app/(app)/_layout.tsx @@ -1,9 +1,13 @@ -import React from 'react' -import { Tabs } from 'expo-router' -import { MaterialCommunityIcons } from '@expo/vector-icons' +import React from 'react'; -const AppLayout = () => { +import { MaterialCommunityIcons } from '@expo/vector-icons'; +import { Tabs } from 'expo-router'; + +const HomeTabBarIcon = ({ color }: { color: string }) => ( + +); +const AppLayout = () => { return ( { options={{ title: 'Home', headerShown: false, - tabBarIcon: ({ color }) => + tabBarIcon: HomeTabBarIcon }} /> - ) -} + ); +}; -export default AppLayout \ No newline at end of file +export default AppLayout; diff --git a/frontend/sac-mobile/app/(app)/index.tsx b/frontend/sac-mobile/app/(app)/index.tsx index 9a5b9a935..6d4b9d0c0 100644 --- a/frontend/sac-mobile/app/(app)/index.tsx +++ b/frontend/sac-mobile/app/(app)/index.tsx @@ -1,12 +1,15 @@ -import { View, Text } from 'react-native' -import React from 'react' +import { useAuthStore } from '@/hooks/use-auth'; +import React from 'react'; +import { Button, Text, View } from 'react-native'; const Home = () => { + const { logout } = useAuthStore(); return ( - + +