@salesforce/webapp-template-app-react-byo-experimental 1.3.3

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 (101) hide show
  1. package/LICENSE.txt +82 -0
  2. package/dist/.a4drules/build-validation.md +81 -0
  3. package/dist/.a4drules/code-quality.md +150 -0
  4. package/dist/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
  5. package/dist/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +211 -0
  6. package/dist/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
  7. package/dist/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
  8. package/dist/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
  9. package/dist/.a4drules/graphql.md +98 -0
  10. package/dist/.a4drules/images.md +13 -0
  11. package/dist/.a4drules/react.md +361 -0
  12. package/dist/.a4drules/react_image_processing.md +45 -0
  13. package/dist/.a4drules/typescript.md +224 -0
  14. package/dist/.forceignore +15 -0
  15. package/dist/.husky/pre-commit +4 -0
  16. package/dist/.prettierignore +11 -0
  17. package/dist/.prettierrc +17 -0
  18. package/dist/CHANGELOG.md +11 -0
  19. package/dist/README.md +18 -0
  20. package/dist/config/project-scratch-def.json +13 -0
  21. package/dist/force-app/main/default/digitalExperienceConfigs/appreactbyo1.digitalExperienceConfig +8 -0
  22. package/dist/force-app/main/default/digitalExperiences/site/appreactbyo1/appreactbyo1.digitalExperience-meta.xml +11 -0
  23. package/dist/force-app/main/default/digitalExperiences/site/appreactbyo1/sfdc_cms__site/appreactbyo1/_meta.json +5 -0
  24. package/dist/force-app/main/default/digitalExperiences/site/appreactbyo1/sfdc_cms__site/appreactbyo1/content.json +10 -0
  25. package/dist/force-app/main/default/networks/appreactbyo.network +60 -0
  26. package/dist/force-app/main/default/package.xml +20 -0
  27. package/dist/force-app/main/default/sites/appreactbyo.site +31 -0
  28. package/dist/force-app/main/default/webapplications/appreactbyo/.prettierignore +9 -0
  29. package/dist/force-app/main/default/webapplications/appreactbyo/.prettierrc +11 -0
  30. package/dist/force-app/main/default/webapplications/appreactbyo/appreactbyo.webapplication-meta.xml +7 -0
  31. package/dist/force-app/main/default/webapplications/appreactbyo/eslint.config.js +113 -0
  32. package/dist/force-app/main/default/webapplications/appreactbyo/index.html +13 -0
  33. package/dist/force-app/main/default/webapplications/appreactbyo/package.json +42 -0
  34. package/dist/force-app/main/default/webapplications/appreactbyo/src/api/graphql-operations-types.ts +127 -0
  35. package/dist/force-app/main/default/webapplications/appreactbyo/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
  36. package/dist/force-app/main/default/webapplications/appreactbyo/src/app.tsx +16 -0
  37. package/dist/force-app/main/default/webapplications/appreactbyo/src/appLayout.tsx +11 -0
  38. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/icons/book.svg +3 -0
  39. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/icons/copy.svg +4 -0
  40. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/icons/rocket.svg +3 -0
  41. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/icons/star.svg +3 -0
  42. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/images/codey-1.png +0 -0
  43. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/images/codey-2.png +0 -0
  44. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/images/codey-3.png +0 -0
  45. package/dist/force-app/main/default/webapplications/appreactbyo/src/assets/images/vibe-codey.svg +194 -0
  46. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/alerts/status-alert.tsx +45 -0
  47. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/auth/authentication-route.tsx +21 -0
  48. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/auth/private-route.tsx +36 -0
  49. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/footers/footer-link.tsx +36 -0
  50. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/forms/auth-form.tsx +79 -0
  51. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/forms/submit-button.tsx +49 -0
  52. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/layout/card-layout.tsx +23 -0
  53. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/layout/centered-page-layout.tsx +73 -0
  54. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/layout/loading-page.tsx +46 -0
  55. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/alert.tsx +65 -0
  56. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/button.tsx +56 -0
  57. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/card.tsx +77 -0
  58. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/field.tsx +111 -0
  59. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/index.ts +71 -0
  60. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/input.tsx +19 -0
  61. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/label.tsx +19 -0
  62. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/pagination.tsx +99 -0
  63. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/select.tsx +151 -0
  64. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/skeleton.tsx +7 -0
  65. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/spinner.tsx +21 -0
  66. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/table.tsx +114 -0
  67. package/dist/force-app/main/default/webapplications/appreactbyo/src/components/ui/tabs.tsx +115 -0
  68. package/dist/force-app/main/default/webapplications/appreactbyo/src/context/AuthContext.tsx +83 -0
  69. package/dist/force-app/main/default/webapplications/appreactbyo/src/hooks/form.tsx +116 -0
  70. package/dist/force-app/main/default/webapplications/appreactbyo/src/lib/utils.ts +6 -0
  71. package/dist/force-app/main/default/webapplications/appreactbyo/src/navigationMenu.tsx +81 -0
  72. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/About.tsx +12 -0
  73. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/ChangePassword.tsx +105 -0
  74. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/ForgotPassword.tsx +67 -0
  75. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/Home.tsx +12 -0
  76. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/Login.tsx +84 -0
  77. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/NotFound.tsx +18 -0
  78. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/Profile.tsx +146 -0
  79. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/Register.tsx +117 -0
  80. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/ResetPassword.tsx +101 -0
  81. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/about.tsx +10 -0
  82. package/dist/force-app/main/default/webapplications/appreactbyo/src/pages/new.tsx +10 -0
  83. package/dist/force-app/main/default/webapplications/appreactbyo/src/router-utils.tsx +34 -0
  84. package/dist/force-app/main/default/webapplications/appreactbyo/src/routes.tsx +88 -0
  85. package/dist/force-app/main/default/webapplications/appreactbyo/src/styles/global.css +94 -0
  86. package/dist/force-app/main/default/webapplications/appreactbyo/src/utils/authenticationConfig.ts +52 -0
  87. package/dist/force-app/main/default/webapplications/appreactbyo/src/utils/helpers.ts +161 -0
  88. package/dist/force-app/main/default/webapplications/appreactbyo/tsconfig.json +36 -0
  89. package/dist/force-app/main/default/webapplications/appreactbyo/tsconfig.node.json +13 -0
  90. package/dist/force-app/main/default/webapplications/appreactbyo/vite-env.d.ts +1 -0
  91. package/dist/force-app/main/default/webapplications/appreactbyo/vite.config.ts +82 -0
  92. package/dist/force-app/main/default/webapplications/appreactbyo/vitest-env.d.ts +2 -0
  93. package/dist/force-app/main/default/webapplications/appreactbyo/vitest.config.ts +11 -0
  94. package/dist/force-app/main/default/webapplications/appreactbyo/vitest.setup.ts +1 -0
  95. package/dist/force-app/main/default/webapplications/appreactbyo/webapplication.json +7 -0
  96. package/dist/jest.config.js +6 -0
  97. package/dist/package.json +37 -0
  98. package/dist/scripts/apex/hello.apex +10 -0
  99. package/dist/scripts/soql/account.soql +6 -0
  100. package/dist/sfdx-project.json +12 -0
  101. package/package.json +28 -0
@@ -0,0 +1,146 @@
1
+ import { useState, useEffect } from "react";
2
+ import { z } from "zod";
3
+
4
+ // [Dev Note] These are standard Salesforce SDK methods that interact with the UI API.
5
+ // They respect field-level security and validation rules defined in Salesforce.
6
+ import { getRecord, updateRecord } from "@salesforce/webapp-experimental/api";
7
+
8
+ import { CenteredPageLayout } from "../components/layout/centered-page-layout";
9
+ import { LoadingPage } from "../components/layout/loading-page";
10
+ import { AuthForm } from "../components/forms/auth-form";
11
+ import { useAppForm } from "../hooks/form";
12
+ import { ROUTES } from "../utils/authenticationConfig";
13
+ import { emailSchema, flattenUiApiRecord, getErrorMessage } from "../utils/helpers";
14
+ import { getUser } from "../context/AuthContext";
15
+
16
+ const optionalString = z
17
+ .string()
18
+ .trim()
19
+ .transform((val) => (val === "" ? null : val))
20
+ .nullable()
21
+ .optional();
22
+
23
+ const profileSchema = z.object({
24
+ FirstName: z.string().trim().min(1, "First name is required"),
25
+ LastName: z.string().trim().min(1, "Last name is required"),
26
+ Email: emailSchema,
27
+ Phone: optionalString,
28
+ Street: optionalString,
29
+ City: optionalString,
30
+ State: optionalString,
31
+ PostalCode: optionalString,
32
+ Country: optionalString,
33
+ });
34
+
35
+ type ProfileFormValues = z.infer<typeof profileSchema>;
36
+
37
+ export default function Profile() {
38
+ const user = getUser();
39
+ const [profile, setProfile] = useState<ProfileFormValues | null>(null);
40
+ const [loadError, setLoadError] = useState<string | null>(null);
41
+ const [success, setSuccess] = useState(false);
42
+ const [submitError, setSubmitError] = useState<string | null>(null);
43
+
44
+ const form = useAppForm({
45
+ defaultValues: {} as ProfileFormValues,
46
+ validators: { onChange: profileSchema, onSubmit: profileSchema },
47
+ onSubmit: async ({ value }) => {
48
+ setSubmitError(null);
49
+ setSuccess(false);
50
+ try {
51
+ // [Dev Note] updateRecord automatically handles the PATCH request to the UI API.
52
+ // It expects the Record ID (user.id) and an object of field values.
53
+ const record = await updateRecord(user.id, value);
54
+
55
+ // [Dev Note] We flatten the complex UI API response structure for easier local use.
56
+ setProfile(flattenUiApiRecord(record));
57
+
58
+ setSuccess(true);
59
+ // Scroll to top of page after successful update so user sees it
60
+ window.scrollTo({ top: 0, behavior: "smooth" });
61
+ } catch (err) {
62
+ setSubmitError(getErrorMessage(err, "Failed to update profile"));
63
+ }
64
+ },
65
+ onSubmitInvalid: () => {},
66
+ });
67
+
68
+ useEffect(() => {
69
+ let mounted = true;
70
+
71
+ // [Dev Note] Fetch the user record fields. "layoutTypes: 'Full'" asks for the default layout fields.
72
+ // Ensure the authenticated user has Read access to these fields in Salesforce.
73
+ getRecord(user.id, { layoutTypes: "Full" })
74
+ .then((record: any) => mounted && setProfile(flattenUiApiRecord(record)))
75
+ .catch((err: any) => {
76
+ if (mounted) {
77
+ setLoadError(getErrorMessage(err, "Failed to load profile"));
78
+ } else {
79
+ console.error("Failed to load profile", err);
80
+ }
81
+ });
82
+ return () => {
83
+ mounted = false;
84
+ };
85
+ }, [user.id]);
86
+
87
+ useEffect(() => {
88
+ if (profile) {
89
+ const formData = profileSchema.parse(profile);
90
+ form.reset(formData);
91
+ }
92
+ }, [profile]);
93
+
94
+ if (!profile && !loadError) {
95
+ return <LoadingPage contentMaxWidth="md" loadingText="Loading profile…" />;
96
+ }
97
+
98
+ return (
99
+ <CenteredPageLayout contentMaxWidth="md" title={ROUTES.PROFILE.TITLE}>
100
+ <form.AppForm>
101
+ <AuthForm
102
+ title="Profile"
103
+ description="Update your account information"
104
+ error={loadError ?? submitError}
105
+ success={success && "Profile updated!"}
106
+ submit={{ text: "Save Changes", loadingText: "Saving…" }}
107
+ footer={{ link: ROUTES.CHANGE_PASSWORD.PATH, linkText: "Change password" }}
108
+ >
109
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
110
+ <form.AppField name="FirstName">
111
+ {(field) => <field.TextField label="First name" autoComplete="given-name" />}
112
+ </form.AppField>
113
+ <form.AppField name="LastName">
114
+ {(field) => <field.TextField label="Last name" autoComplete="family-name" />}
115
+ </form.AppField>
116
+ </div>
117
+ <form.AppField name="Email">
118
+ {(field) => <field.EmailField label="Email" />}
119
+ </form.AppField>
120
+ <form.AppField name="Phone">
121
+ {(field) => <field.TextField label="Phone" type="tel" autoComplete="tel" />}
122
+ </form.AppField>
123
+ <form.AppField name="Street">
124
+ {(field) => <field.TextField label="Street" autoComplete="street-address" />}
125
+ </form.AppField>
126
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
127
+ <form.AppField name="City">
128
+ {(field) => <field.TextField label="City" autoComplete="address-level2" />}
129
+ </form.AppField>
130
+ <form.AppField name="State">
131
+ {(field) => <field.TextField label="State" autoComplete="address-level1" />}
132
+ </form.AppField>
133
+ </div>
134
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
135
+ <form.AppField name="PostalCode">
136
+ {(field) => <field.TextField label="Postal Code" autoComplete="postal-code" />}
137
+ </form.AppField>
138
+ <form.AppField name="Country">
139
+ {(field) => <field.TextField label="Country" autoComplete="country-name" />}
140
+ </form.AppField>
141
+ </div>
142
+ </AuthForm>
143
+ </form.AppForm>
144
+ </CenteredPageLayout>
145
+ );
146
+ }
@@ -0,0 +1,117 @@
1
+ import { useState } from "react";
2
+ import { useNavigate, useSearchParams } from "react-router";
3
+ import { z } from "zod";
4
+ import { baseClient } from "@salesforce/webapp-experimental/api";
5
+ import { CenteredPageLayout } from "../components/layout/centered-page-layout";
6
+ import { AuthForm } from "../components/forms/auth-form";
7
+ import { useAppForm } from "../hooks/form";
8
+ import { ROUTES, AUTH_PLACEHOLDERS } from "../utils/authenticationConfig";
9
+ import {
10
+ emailSchema,
11
+ passwordSchema,
12
+ getStartUrl,
13
+ handleApiResponse,
14
+ getErrorMessage,
15
+ } from "../utils/helpers";
16
+
17
+ const registerSchema = z
18
+ .object({
19
+ firstName: z.string().trim().min(1, "First name is required"),
20
+ lastName: z.string().trim().min(1, "Last name is required"),
21
+ email: emailSchema,
22
+ password: passwordSchema,
23
+ confirmPassword: z.string().min(1, "Please confirm your password"),
24
+ })
25
+ .refine((data) => data.password === data.confirmPassword, {
26
+ message: "Passwords do not match",
27
+ path: ["confirmPassword"],
28
+ });
29
+
30
+ export default function Register() {
31
+ const navigate = useNavigate();
32
+ const [searchParams] = useSearchParams();
33
+ const [submitError, setSubmitError] = useState<string | null>(null);
34
+
35
+ const form = useAppForm({
36
+ defaultValues: { firstName: "", lastName: "", email: "", password: "", confirmPassword: "" },
37
+ validators: { onChange: registerSchema, onSubmit: registerSchema },
38
+ onSubmit: async ({ value }) => {
39
+ setSubmitError(null);
40
+ try {
41
+ // [Dev Note] Calls the custom Apex REST endpoint for user registration.
42
+ // Ensure your Apex logic handles duplicate checks and user creation (e.g., Site.createExternalUser).
43
+ const response = await baseClient.post("/services/apexrest/auth/register", {
44
+ firstName: value.firstName.trim(),
45
+ lastName: value.lastName.trim(),
46
+ email: value.email.trim().toLowerCase(),
47
+ password: value.password,
48
+ });
49
+ await handleApiResponse(response, "Registration failed");
50
+ navigate(getStartUrl(searchParams), { replace: true });
51
+ } catch (err) {
52
+ setSubmitError(getErrorMessage(err, "Registration failed"));
53
+ }
54
+ },
55
+ onSubmitInvalid: () => {},
56
+ });
57
+
58
+ return (
59
+ <CenteredPageLayout title={ROUTES.REGISTER.TITLE}>
60
+ <form.AppForm>
61
+ <AuthForm
62
+ title="Sign Up"
63
+ description="Enter your information to create an account"
64
+ error={submitError}
65
+ submit={{ text: "Create an account", loadingText: "Creating account…" }}
66
+ footer={{
67
+ text: "Already have an account?",
68
+ link: ROUTES.LOGIN.PATH,
69
+ linkText: "Sign in",
70
+ }}
71
+ >
72
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
73
+ <form.AppField name="firstName">
74
+ {(field) => (
75
+ <field.TextField
76
+ label="First name"
77
+ placeholder={AUTH_PLACEHOLDERS.FIRST_NAME}
78
+ autoComplete="given-name"
79
+ />
80
+ )}
81
+ </form.AppField>
82
+ <form.AppField name="lastName">
83
+ {(field) => (
84
+ <field.TextField
85
+ label="Last name"
86
+ placeholder={AUTH_PLACEHOLDERS.LAST_NAME}
87
+ autoComplete="family-name"
88
+ />
89
+ )}
90
+ </form.AppField>
91
+ </div>
92
+ <form.AppField name="email">
93
+ {(field) => <field.EmailField label="Email" />}
94
+ </form.AppField>
95
+ <form.AppField name="password">
96
+ {(field) => (
97
+ <field.PasswordField
98
+ label="Password"
99
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_CREATE}
100
+ autoComplete="new-password"
101
+ />
102
+ )}
103
+ </form.AppField>
104
+ <form.AppField name="confirmPassword">
105
+ {(field) => (
106
+ <field.PasswordField
107
+ label="Confirm Password"
108
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_CONFIRM}
109
+ autoComplete="new-password"
110
+ />
111
+ )}
112
+ </form.AppField>
113
+ </AuthForm>
114
+ </form.AppForm>
115
+ </CenteredPageLayout>
116
+ );
117
+ }
@@ -0,0 +1,101 @@
1
+ import { useState } from "react";
2
+ import { Link, useSearchParams } from "react-router";
3
+ import { baseClient } from "@salesforce/webapp-experimental/api";
4
+ import { CardLayout } from "../components/layout/card-layout";
5
+ import { CenteredPageLayout } from "../components/layout/centered-page-layout";
6
+ import { AuthForm } from "../components/forms/auth-form";
7
+ import { StatusAlert } from "../components/alerts/status-alert";
8
+ import { useAppForm } from "../hooks/form";
9
+ import { ROUTES, AUTH_PLACEHOLDERS } from "../utils/authenticationConfig";
10
+ import { newPasswordSchema, handleApiResponse, getErrorMessage } from "../utils/helpers";
11
+
12
+ export default function ResetPassword() {
13
+ const [searchParams] = useSearchParams();
14
+ const token = searchParams.get("token");
15
+ const [success, setSuccess] = useState(false);
16
+ const [submitError, setSubmitError] = useState<string | null>(null);
17
+
18
+ const form = useAppForm({
19
+ defaultValues: { newPassword: "", confirmPassword: "" },
20
+ validators: { onChange: newPasswordSchema, onSubmit: newPasswordSchema },
21
+ onSubmit: async ({ value }) => {
22
+ setSubmitError(null);
23
+ setSuccess(false);
24
+ try {
25
+ // [Dev Note] Custom Apex Endpoint: /auth/reset-password
26
+ // You must ensure this Apex class exists in your org
27
+ const response = await baseClient.post("/services/apexrest/auth/reset-password", {
28
+ token,
29
+ newPassword: value.newPassword,
30
+ });
31
+ await handleApiResponse(response, "Password reset failed");
32
+ setSuccess(true);
33
+ // Scroll to top of page after successful submission so user sees it
34
+ window.scrollTo({ top: 0, behavior: "smooth" });
35
+ } catch (err) {
36
+ setSubmitError(getErrorMessage(err, "Password reset failed"));
37
+ }
38
+ },
39
+ onSubmitInvalid: () => {},
40
+ });
41
+
42
+ if (!token) {
43
+ return (
44
+ <CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
45
+ <CardLayout title="Reset Password">
46
+ <StatusAlert>
47
+ Reset token is invalid or expired.{" "}
48
+ <Link to={ROUTES.FORGOT_PASSWORD.PATH} className="underline underline-offset-4">
49
+ Request a new password reset link.
50
+ </Link>
51
+ </StatusAlert>
52
+ </CardLayout>
53
+ </CenteredPageLayout>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
59
+ <form.AppForm>
60
+ <AuthForm
61
+ title="Reset Password"
62
+ description="Enter your new password below"
63
+ error={submitError}
64
+ success={
65
+ success && (
66
+ <>
67
+ Password reset successfully!{" "}
68
+ <Link to={ROUTES.LOGIN.PATH} className="underline">
69
+ Sign in
70
+ </Link>
71
+ </>
72
+ )
73
+ }
74
+ submit={{ text: "Reset Password", loadingText: "Resetting…" }}
75
+ footer={{ text: "Remember your password?", link: ROUTES.LOGIN.PATH, linkText: "Sign in" }}
76
+ >
77
+ <form.AppField name="newPassword">
78
+ {(field) => (
79
+ <field.PasswordField
80
+ label="New Password"
81
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW}
82
+ autoComplete="new-password"
83
+ disabled={success}
84
+ />
85
+ )}
86
+ </form.AppField>
87
+ <form.AppField name="confirmPassword">
88
+ {(field) => (
89
+ <field.PasswordField
90
+ label="Confirm Password"
91
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW_CONFIRM}
92
+ autoComplete="new-password"
93
+ disabled={success}
94
+ />
95
+ )}
96
+ </form.AppField>
97
+ </AuthForm>
98
+ </form.AppForm>
99
+ </CenteredPageLayout>
100
+ );
101
+ }
@@ -0,0 +1,10 @@
1
+ export default function About() {
2
+ return (
3
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
4
+ <div className="text-center">
5
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">About</h1>
6
+ <p className="text-lg text-gray-600">This is the about page.</p>
7
+ </div>
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,10 @@
1
+ export default function New() {
2
+ return (
3
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
4
+ <div className="text-center">
5
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">New</h1>
6
+ <p className="text-lg text-gray-600">This is the new page.</p>
7
+ </div>
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,34 @@
1
+ import type { RouteObject } from "react-router";
2
+ import { routes } from "./routes";
3
+
4
+ export type RouteWithFullPath = RouteObject & { fullPath: string };
5
+
6
+ const flatMapRoutes = (route: RouteObject, parentPath: string = ""): RouteWithFullPath[] => {
7
+ // Construct the full path for this route
8
+ let fullPath: string;
9
+
10
+ if (route.index) {
11
+ // Index routes use the parent path
12
+ fullPath = parentPath || "/";
13
+ } else if (route.path) {
14
+ // Combine parent path with current path
15
+ if (route.path.startsWith("/")) {
16
+ fullPath = route.path;
17
+ } else {
18
+ fullPath = parentPath === "/" ? `/${route.path}` : `${parentPath}/${route.path}`;
19
+ }
20
+ } else {
21
+ fullPath = parentPath;
22
+ }
23
+
24
+ const routeWithPath = { ...route, fullPath };
25
+
26
+ // Recursively process children
27
+ const childRoutes = route.children?.flatMap((child) => flatMapRoutes(child, fullPath)) || [];
28
+
29
+ return [routeWithPath, ...childRoutes];
30
+ };
31
+
32
+ export const getAllRoutes = (): RouteWithFullPath[] => {
33
+ return routes.flatMap((route) => flatMapRoutes(route));
34
+ };
@@ -0,0 +1,88 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import AppLayout from './appLayout';
3
+ import Home from '@/pages/Home';
4
+ import About from './pages/about';
5
+ import NotFound from './pages/NotFound';
6
+ import Login from "./pages/Login";
7
+ import Register from "./pages/Register";
8
+ import ForgotPassword from "./pages/ForgotPassword";
9
+ import ResetPassword from "./pages/ResetPassword";
10
+ import Profile from "./pages/Profile";
11
+ import ChangePassword from "./pages/ChangePassword";
12
+ import AuthenticationRoute from "./components/auth/authentication-route";
13
+ import PrivateRoute from "./components/auth/private-route";
14
+ import { ROUTES } from "./utils/authenticationConfig";
15
+ import New from "./pages/new";
16
+
17
+ export const routes: RouteObject[] = [
18
+ {
19
+ path: "/",
20
+ element: <AppLayout />,
21
+ children: [
22
+ {
23
+ index: true,
24
+ element: <Home />,
25
+ handle: { showInNavigation: true, label: 'Home' }
26
+ },
27
+ {
28
+ path: "about",
29
+ element: <About />,
30
+ handle: { showInNavigation: true, label: "About" }
31
+ },
32
+ {
33
+ path: '*',
34
+ element: <NotFound />
35
+ },
36
+ {
37
+ element: <AuthenticationRoute />,
38
+ children: [
39
+ {
40
+ path: ROUTES.LOGIN.PATH,
41
+ element: <Login />,
42
+ handle: { showInNavigation: true, label: "Login", title: ROUTES.LOGIN.TITLE }
43
+ },
44
+ {
45
+ path: ROUTES.REGISTER.PATH,
46
+ element: <Register />,
47
+ handle: { showInNavigation: false, title: ROUTES.REGISTER.TITLE }
48
+ },
49
+ {
50
+ path: ROUTES.FORGOT_PASSWORD.PATH,
51
+ element: <ForgotPassword />,
52
+ handle: { showInNavigation: false, title: ROUTES.FORGOT_PASSWORD.TITLE }
53
+ },
54
+ {
55
+ path: ROUTES.RESET_PASSWORD.PATH,
56
+ element: <ResetPassword />,
57
+ handle: { showInNavigation: false, title: ROUTES.RESET_PASSWORD.TITLE }
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ element: <PrivateRoute />,
63
+ children: [
64
+ {
65
+ path: ROUTES.PROFILE.PATH,
66
+ element: <Profile />,
67
+ handle: { showInNavigation: true, label: "Profile", title: ROUTES.PROFILE.TITLE }
68
+ },
69
+ {
70
+ path: ROUTES.CHANGE_PASSWORD.PATH,
71
+ element: <ChangePassword />,
72
+ handle: { showInNavigation: false, title: ROUTES.CHANGE_PASSWORD.TITLE }
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ path: "contact",
78
+ element: <h1>Hello World from Contact Page</h1>,
79
+ handle: { showInNavigation: false }
80
+ },
81
+ {
82
+ path: "new",
83
+ element: <New />,
84
+ handle: { showInNavigation: true, label: "New Page" }
85
+ }
86
+ ]
87
+ }
88
+ ];
@@ -0,0 +1,94 @@
1
+ @import "tailwindcss";
2
+ @layer base {
3
+ :root {
4
+ /* browser system elements (scrollbars, etc) */
5
+ color-scheme: light;
6
+
7
+ --background: oklch(1 0 0);
8
+ --foreground: oklch(0.145 0 0);
9
+ --card: oklch(1 0 0);
10
+ --card-foreground: oklch(0.145 0 0);
11
+ --popover: oklch(1 0 0);
12
+ --popover-foreground: oklch(0.145 0 0);
13
+ --primary: oklch(0.205 0 0);
14
+ --primary-foreground: oklch(0.985 0 0);
15
+ --secondary: oklch(0.97 0 0);
16
+ --secondary-foreground: oklch(0.205 0 0);
17
+ --muted: oklch(0.97 0 0);
18
+ --muted-foreground: oklch(0.556 0 0);
19
+ --accent: oklch(0.97 0 0);
20
+ --accent-foreground: oklch(0.205 0 0);
21
+ --destructive: oklch(0.577 0.245 27.325);
22
+ /* 1. Maintenance: Added missing token for accessible text on red backgrounds */
23
+ --destructive-foreground: oklch(0.985 0 0);
24
+ --border: oklch(0.922 0 0);
25
+ --input: oklch(0.922 0 0);
26
+ --ring: oklch(0.205 0 0);
27
+ --radius: 0.625rem;
28
+ --spacing: 0.25rem;
29
+ }
30
+ .dark {
31
+ /* browser system elements */
32
+ color-scheme: dark;
33
+
34
+ --background: oklch(0.145 0 0);
35
+ --foreground: oklch(0.985 0 0);
36
+ --card: oklch(0.145 0 0);
37
+ --card-foreground: oklch(0.985 0 0);
38
+ --popover: oklch(0.145 0 0);
39
+ --popover-foreground: oklch(0.985 0 0);
40
+ --primary: oklch(0.985 0 0);
41
+ --primary-foreground: oklch(0.205 0 0);
42
+ --secondary: oklch(0.269 0 0);
43
+ --secondary-foreground: oklch(0.985 0 0);
44
+ --muted: oklch(0.269 0 0);
45
+ --muted-foreground: oklch(0.708 0 0);
46
+ --accent: oklch(0.269 0 0);
47
+ --accent-foreground: oklch(0.985 0 0);
48
+ --destructive: oklch(0.396 0.141 25.723);
49
+ /* Dark mode text for destructive actions */
50
+ --destructive-foreground: oklch(0.985 0 0);
51
+ --accent-foreground: oklch(0.985 0 0);
52
+ }
53
+ /* 2. Accessibility: Global reset for users who prefer reduced motion */
54
+ @media (prefers-reduced-motion: reduce) {
55
+ * {
56
+ animation-duration: 0.01ms !important;
57
+ animation-iteration-count: 1 !important;
58
+ transition-duration: 0.01ms !important;
59
+ scroll-behavior: auto !important;
60
+ }
61
+ }
62
+ * {
63
+ @apply border-[var(--border)];
64
+ }
65
+ body {
66
+ @apply bg-[var(--background)] text-[var(--foreground)] antialiased;
67
+ }
68
+ }
69
+
70
+ @theme inline {
71
+ --color-background: var(--background);
72
+ --color-foreground: var(--foreground);
73
+ --color-card: var(--card);
74
+ --color-card-foreground: var(--card-foreground);
75
+ --color-popover: var(--popover);
76
+ --color-popover-foreground: var(--popover-foreground);
77
+ --color-primary: var(--primary);
78
+ --color-primary-foreground: var(--primary-foreground);
79
+ --color-secondary: var(--secondary);
80
+ --color-secondary-foreground: var(--secondary-foreground);
81
+ --color-muted: var(--muted);
82
+ --color-muted-foreground: var(--muted-foreground);
83
+ --color-accent: var(--accent);
84
+ --color-accent-foreground: var(--accent-foreground);
85
+ --color-destructive: var(--destructive);
86
+ --color-border: var(--border);
87
+ --color-input: var(--input);
88
+ --color-ring: var(--ring);
89
+ --radius-sm: calc(var(--radius) - 4px);
90
+ --radius-md: calc(var(--radius) - 2px);
91
+ --radius-lg: var(--radius);
92
+ --radius-xl: calc(var(--radius) + 4px);
93
+ --spacing: var(--spacing);
94
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * [Dev Note] Centralized configuration for Auth routes.
3
+ * Each route contains both the path and page title.
4
+ * Using constants prevents typos in route paths across the application.
5
+ */
6
+ export const ROUTES = {
7
+ LOGIN: {
8
+ PATH: "/login",
9
+ TITLE: "Login | MyApp",
10
+ },
11
+ REGISTER: {
12
+ PATH: "/register",
13
+ TITLE: "Create Account | MyApp",
14
+ },
15
+ FORGOT_PASSWORD: {
16
+ PATH: "/forgot-password",
17
+ TITLE: "Recover Password | MyApp",
18
+ },
19
+ RESET_PASSWORD: {
20
+ PATH: "/reset-password",
21
+ TITLE: "Reset Password | MyApp",
22
+ },
23
+ PROFILE: {
24
+ PATH: "/profile",
25
+ TITLE: "My Profile | MyApp",
26
+ },
27
+ CHANGE_PASSWORD: {
28
+ PATH: "/change-password",
29
+ TITLE: "Change Password | MyApp",
30
+ },
31
+ } as const;
32
+
33
+ /**
34
+ * [Dev Note] Query parameter key used to store the return URL.
35
+ * e.g. /login?startUrl=/profile
36
+ */
37
+ export const AUTH_REDIRECT_PARAM = "startUrl";
38
+
39
+ /**
40
+ * Placeholder text constants for authentication form inputs.
41
+ */
42
+ export const AUTH_PLACEHOLDERS = {
43
+ EMAIL: "asalesforce@example.com",
44
+ PASSWORD: "",
45
+ PASSWORD_CREATE: "",
46
+ PASSWORD_CONFIRM: "",
47
+ PASSWORD_NEW: "",
48
+ PASSWORD_NEW_CONFIRM: "",
49
+ FIRST_NAME: "Astro",
50
+ LAST_NAME: "Salesforce",
51
+ USERNAME: "asalesforce",
52
+ } as const;