rogasper commited on
Commit
2b6b173
·
1 Parent(s): bb6e880

feat: enhance password validation across forms and improve button styling. Update password requirements to include uppercase, lowercase, and numeric characters in sign-up and account settings. Adjust button styles in sign-in and sign-up forms for a consistent look.

Browse files
apps/web/src/components/TourGuide.tsx CHANGED
@@ -32,7 +32,7 @@ const globalSteps: Step[] = [
32
  { target: "[data-tour='nav-bank']", title: "Bank Soal — Buat Paket", content: "Atur soal-soal kamu jadi paket latihan dari bank soal.", spotlightPadding: 4 },
33
  { target: "[data-tour='nav-packages']", title: "Paket Soal — Mulai Latihan", content: "Temukan paket soal dari komunitas atau buatan sendiri.", spotlightPadding: 4 },
34
  { target: "[data-tour='nav-analytics']", title: "Analytics — Evaluasi", content: "Lihat analitik mendalam: skor, tren, dan rekomendasi belajar.", spotlightPadding: 4 },
35
- { target: "body" as any, placement: "center", title: "Siap Belajar?", content: "Setiap halaman punya panduan sendiri. Cari tombol <strong>?</strong> di pojok kanan bawah halaman!", hideOverlay: true },
36
  ];
37
 
38
  export function TourGuide() {
 
32
  { target: "[data-tour='nav-bank']", title: "Bank Soal — Buat Paket", content: "Atur soal-soal kamu jadi paket latihan dari bank soal.", spotlightPadding: 4 },
33
  { target: "[data-tour='nav-packages']", title: "Paket Soal — Mulai Latihan", content: "Temukan paket soal dari komunitas atau buatan sendiri.", spotlightPadding: 4 },
34
  { target: "[data-tour='nav-analytics']", title: "Analytics — Evaluasi", content: "Lihat analitik mendalam: skor, tren, dan rekomendasi belajar.", spotlightPadding: 4 },
35
+ { target: "body" as any, placement: "center", title: "Siap Belajar?", content: "Setiap halaman punya panduan sendiri. Cari tombol ? di pojok kanan bawah halaman!", hideOverlay: true },
36
  ];
37
 
38
  export function TourGuide() {
apps/web/src/components/settings/AccountSettings.tsx CHANGED
@@ -20,8 +20,8 @@ export function AccountSettings() {
20
 
21
  const handleChangePassword = async (e: React.FormEvent) => {
22
  e.preventDefault();
23
- if (newPassword.length < 8) {
24
- toast.error("Password minimal 8 karakter");
25
  return;
26
  }
27
  if (newPassword !== confirmPassword) {
@@ -123,6 +123,7 @@ export function AccountSettings() {
123
  </div>
124
  <Button
125
  type="submit"
 
126
  disabled={isChangingPassword || !currentPassword || !newPassword || !confirmPassword}
127
  >
128
  {isChangingPassword ? "Menyimpan..." : "Ubah Password"}
@@ -150,7 +151,7 @@ export function AccountSettings() {
150
  placeholder="nama@email.com"
151
  />
152
  </div>
153
- <Button type="submit" disabled={isChangingEmail || !newEmail}>
154
  {isChangingEmail ? "Menyimpan..." : "Ubah Email"}
155
  </Button>
156
  </form>
 
20
 
21
  const handleChangePassword = async (e: React.FormEvent) => {
22
  e.preventDefault();
23
+ if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(newPassword)) {
24
+ toast.error("Password minimal 8 karakter, mengandung huruf besar, huruf kecil, dan angka");
25
  return;
26
  }
27
  if (newPassword !== confirmPassword) {
 
123
  </div>
124
  <Button
125
  type="submit"
126
+ className="rounded-[var(--radius-lg)]"
127
  disabled={isChangingPassword || !currentPassword || !newPassword || !confirmPassword}
128
  >
129
  {isChangingPassword ? "Menyimpan..." : "Ubah Password"}
 
151
  placeholder="nama@email.com"
152
  />
153
  </div>
154
+ <Button type="submit" className="rounded-[var(--radius-lg)]" disabled={isChangingEmail || !newEmail}>
155
  {isChangingEmail ? "Menyimpan..." : "Ubah Email"}
156
  </Button>
157
  </form>
apps/web/src/components/sign-in-form.tsx CHANGED
@@ -130,7 +130,7 @@ export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: ()
130
  selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
131
  >
132
  {({ canSubmit, isSubmitting }) => (
133
- <Button type="submit" className="w-full" disabled={!canSubmit || isSubmitting}>
134
  {isSubmitting ? "Submitting..." : "Sign In"}
135
  </Button>
136
  )}
 
130
  selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
131
  >
132
  {({ canSubmit, isSubmitting }) => (
133
+ <Button type="submit" className="w-full rounded-[var(--radius-lg)]" disabled={!canSubmit || isSubmitting}>
134
  {isSubmitting ? "Submitting..." : "Sign In"}
135
  </Button>
136
  )}
apps/web/src/components/sign-up-form.tsx CHANGED
@@ -54,7 +54,7 @@ export default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: ()
54
  onSubmit: z.object({
55
  name: z.string().min(2, "Name must be at least 2 characters"),
56
  email: z.email("Invalid email address"),
57
- password: z.string().min(8, "Password must be at least 8 characters"),
58
  }),
59
  },
60
  });
@@ -150,7 +150,7 @@ export default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: ()
150
  selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
151
  >
152
  {({ canSubmit, isSubmitting }) => (
153
- <Button type="submit" className="w-full" disabled={!canSubmit || isSubmitting}>
154
  {isSubmitting ? "Submitting..." : "Sign Up"}
155
  </Button>
156
  )}
 
54
  onSubmit: z.object({
55
  name: z.string().min(2, "Name must be at least 2 characters"),
56
  email: z.email("Invalid email address"),
57
+ password: z.string().regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/, "Password must be at least 8 characters with uppercase, lowercase, and number"),
58
  }),
59
  },
60
  });
 
150
  selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
151
  >
152
  {({ canSubmit, isSubmitting }) => (
153
+ <Button type="submit" className="w-full rounded-[var(--radius-lg)]" disabled={!canSubmit || isSubmitting}>
154
  {isSubmitting ? "Submitting..." : "Sign Up"}
155
  </Button>
156
  )}
apps/web/src/routes/__root.tsx CHANGED
@@ -8,7 +8,6 @@ import { Sidebar } from "@/components/sidebar";
8
  import { ThemeProvider } from "@/components/theme-provider";
9
  import { useSidebar } from "@/hooks/use-sidebar";
10
  import { GlobalGenerationProgress } from "@/components/generate/GlobalGenerationProgress";
11
- import { TourGuide } from "@/components/TourGuide";
12
  import type { trpc } from "@/utils/trpc";
13
 
14
  import "../index.css";
@@ -75,7 +74,6 @@ function RootComponent() {
75
  </div>
76
  )}
77
  <GlobalGenerationProgress />
78
- <TourGuide />
79
  <Toaster richColors />
80
  </ThemeProvider>
81
  <TanStackRouterDevtools position="bottom-left" />
 
8
  import { ThemeProvider } from "@/components/theme-provider";
9
  import { useSidebar } from "@/hooks/use-sidebar";
10
  import { GlobalGenerationProgress } from "@/components/generate/GlobalGenerationProgress";
 
11
  import type { trpc } from "@/utils/trpc";
12
 
13
  import "../index.css";
 
74
  </div>
75
  )}
76
  <GlobalGenerationProgress />
 
77
  <Toaster richColors />
78
  </ThemeProvider>
79
  <TanStackRouterDevtools position="bottom-left" />
apps/web/src/routes/forgot-password.tsx CHANGED
@@ -133,8 +133,8 @@ function StepReset({ email }: { email: string }) {
133
  toast.error("Masukkan 6 digit kode OTP");
134
  return;
135
  }
136
- if (newPassword.length < 8) {
137
- toast.error("Password minimal 8 karakter");
138
  return;
139
  }
140
  if (newPassword !== confirmPassword) {
 
133
  toast.error("Masukkan 6 digit kode OTP");
134
  return;
135
  }
136
+ if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(newPassword)) {
137
+ toast.error("Password minimal 8 karakter, mengandung huruf besar, huruf kecil, dan angka");
138
  return;
139
  }
140
  if (newPassword !== confirmPassword) {
apps/web/src/routes/index.tsx CHANGED
@@ -5,6 +5,7 @@ import { trpc } from "@/utils/trpc";
5
  import { Button } from "@labas/ui/components/button";
6
  import { Card, CardContent } from "@labas/ui/components/card";
7
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
 
8
 
9
  export const Route = createFileRoute("/")({
10
  component: HomeComponent,
@@ -384,6 +385,8 @@ function HomeComponent() {
384
  </Link>
385
  </div>
386
  )}
 
 
387
  </div>
388
  );
389
  }
 
5
  import { Button } from "@labas/ui/components/button";
6
  import { Card, CardContent } from "@labas/ui/components/card";
7
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
8
+ import { TourGuide } from "@/components/TourGuide";
9
 
10
  export const Route = createFileRoute("/")({
11
  component: HomeComponent,
 
385
  </Link>
386
  </div>
387
  )}
388
+
389
+ <TourGuide />
390
  </div>
391
  );
392
  }
apps/web/src/routes/me.tsx CHANGED
@@ -222,6 +222,19 @@ function MeComponent() {
222
  </CardContent>
223
  </Card>
224
  </Link>
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  </div>
226
  </div>
227
  );
 
222
  </CardContent>
223
  </Card>
224
  </Link>
225
+ <Link to="/settings" search={{ tab: "account" }} className="block">
226
+ <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[var(--radius-xl)]">
227
+ <CardContent className="p-5 flex items-center gap-4">
228
+ <div className="w-10 h-10 rounded-[var(--radius-lg)] bg-[var(--clay-black)] flex items-center justify-center">
229
+ <MaterialIcon name="settings" className="text-[var(--pure-white)]" />
230
+ </div>
231
+ <div>
232
+ <h3 className="font-semibold text-[var(--clay-black)]">Pengaturan Akun</h3>
233
+ <p className="text-xs text-[var(--warm-charcoal)]">Ubah password, email, dan lainnya</p>
234
+ </div>
235
+ </CardContent>
236
+ </Card>
237
+ </Link>
238
  </div>
239
  </div>
240
  );
packages/api/src/routers/verification.ts CHANGED
@@ -122,7 +122,7 @@ export const verificationRouter = router({
122
  z.object({
123
  email: z.string().email(),
124
  otp: z.string().length(6),
125
- newPassword: z.string().min(8),
126
  }),
127
  )
128
  .mutation(async ({ input }) => {
 
122
  z.object({
123
  email: z.string().email(),
124
  otp: z.string().length(6),
125
+ newPassword: z.string().regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/, "Password must be at least 8 characters with uppercase, lowercase, and number"),
126
  }),
127
  )
128
  .mutation(async ({ input }) => {