Skip to content

Commit 227bef3

Browse files
committed
various changes
1 parent 7e591ae commit 227bef3

32 files changed

+1287
-640
lines changed

TODO.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
# TODO NOW
1+
# Bugs
22

3-
- if a user has purcahsed a course, show a message in the /purchase page so they don't get confused if they happen to navigate to that page on accident and do not show the buy now button or pricing there. instead show a button which will redirect them to the learn/$first-slug (use the slug hook)
4-
- ability to download the entire video as a zip
5-
- if on last segment, show a "complete course" button and navigate to a certificate page
6-
- after clicking the Next Video button, I need to invalidate the queryClient cache for the "progress" entries
7-
- refactor the approach to instead just fetch the array of progress entries instead of doing the left join stuff
8-
- rename progress to completed_segments
3+
- when deleting a segment the app crashes (I think it's related to logic for redirecting the user to a previous segment)
4+
- when reordering modules, the navigation buttons do not seem to navigate the correct order of modules... are we sorting the modules by order? are we correctly updating the order on modules when we re-arrange them?
95

10-
# Tech Debt
6+
# Features
117

12-
- clean up the code and put all .sh scripts in /infra directory
13-
- update the setup.sh script to also install docker compose, etc
8+
- add metric that users can see how many other users completed this segment (help drive more motivation maybe?)
9+
- add the ability for an admin to email all the premium users in the system to let them know new segments or modules are created. Add some type of MJLM editor / preview feature. This should use AWS SES. Provide a generic update template one can start editing. Hopefully support markdown to generate the MJML and send out the styled emails.
10+
- analytics for admins to see which students are finishing which segments
11+
- analytics for admins to get notifications when students comments on segments with a built in way to respond directly on the same notification page
12+
- analytics for when users view the site, sign up, and purchase by day
13+
- analytics on how the ratio between views, sign ups, premiums
14+
- gamification maybe? badges?
15+
- certificates on completion?
16+
- the ability to pick custom lucide-icons for the modules
17+
- editing the segment should change the nested layout, not go to a new page
18+
- experiment with having "new segment" show a modal with the form

docs/design.md

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,6 @@ This document outlines the comprehensive design system used for the learning pla
5656

5757
## 🧩 Component Patterns
5858

59-
### Card System
60-
61-
```css
62-
.module-card {
63-
@apply relative overflow-hidden rounded-xl
64-
bg-gradient-to-br from-white to-gray-50/50
65-
dark:from-gray-900 dark:to-gray-800/50
66-
border border-gray-200/60 dark:border-gray-700/60
67-
shadow-elevation-2 hover:shadow-elevation-3
68-
transition-all duration-300;
69-
}
70-
```
71-
7259
### Button Variants
7360

7461
- **Primary**: `btn-gradient` (Green gradient with glow effects)

src/components/ui/alert-dialog.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,53 @@ const AlertDialogOverlay = React.forwardRef<
2525
));
2626
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
2727

28+
type AnimationType =
29+
| "fade"
30+
| "slide-left"
31+
| "slide-right"
32+
| "slide-in-bottom-left"
33+
| "slide-in-top-left"
34+
| "slide-in-top-right"
35+
| "slide-in-bottom-right";
36+
37+
const getAnimationClasses = (animation: AnimationType) => {
38+
const baseClasses =
39+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
40+
41+
switch (animation) {
42+
case "fade":
43+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95`;
44+
case "slide-left":
45+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2`;
46+
case "slide-right":
47+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=open]:slide-in-from-right-1/2`;
48+
case "slide-in-bottom-left":
49+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-bottom-[48%]`;
50+
case "slide-in-top-left":
51+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`;
52+
case "slide-in-top-right":
53+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-right-1/2 data-[state=open]:slide-in-from-top-[48%]`;
54+
case "slide-in-bottom-right":
55+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-right-1/2 data-[state=open]:slide-in-from-bottom-[48%]`;
56+
default:
57+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2`;
58+
}
59+
};
60+
2861
const AlertDialogContent = React.forwardRef<
2962
React.ElementRef<typeof AlertDialogPrimitive.Content>,
30-
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31-
>(({ className, ...props }, ref) => (
63+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> & {
64+
animation?: AnimationType;
65+
}
66+
>(({ className, animation = "slide-left", ...props }, ref) => (
3267
<AlertDialogPortal>
3368
<AlertDialogOverlay />
3469
<AlertDialogPrimitive.Content
3570
ref={ref}
3671
className={cn(
37-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
72+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200",
73+
getAnimationClasses(animation),
74+
"sm:rounded-lg",
3875
className
3976
)}
4077
{...props}

src/components/ui/button.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ import { cva, type VariantProps } from "class-variance-authority";
44

55
import { cn } from "~/lib/utils";
66

7+
// TODO: DO NOT REMOVE, THIS IS A STYLE I THOUGHT WAS PRETTY SLICK
8+
// module-card px-4 py-2 flex items-center gap-2 text-sm font-medium text-theme-700 dark:text-theme-300 hover:text-theme-800 dark:hover:text-theme-200 transition-all duration-200 hover:shadow-elevation-3
9+
710
const buttonVariants = cva(
8-
"rounded-lg inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11+
"cursor-pointer rounded-lg inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
912
{
1013
variants: {
1114
variant: {
1215
default:
13-
"bg-theme-500 text-black shadow hover:bg-theme-600 dark:bg-theme-400 dark:hover:bg-theme-500",
16+
"w-full justify-center module-card px-4 py-2 text-theme-700 dark:text-theme-300 hover:text-theme-800 dark:hover:text-theme-200 transition-all duration-200 hover:shadow-elevation-3",
1417
destructive:
15-
"bg-red-500 text-white shadow-sm hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700",
16-
"destructive-outline":
1718
"border border-red-500 bg-transparent text-red-500 shadow-sm hover:bg-red-50 hover:text-red-600 dark:border-red-400 dark:text-red-400 dark:hover:bg-red-950/50 dark:hover:text-red-300",
1819
outline:
1920
"border border-theme-200 bg-background shadow-sm hover:bg-theme-100 hover:text-theme-700 dark:border-theme-800 dark:hover:bg-theme-950 dark:hover:text-theme-300",

src/components/ui/dialog.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,53 @@ const DialogOverlay = React.forwardRef<
2727
));
2828
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
2929

30+
type DialogAnimationType =
31+
| "fade"
32+
| "slide-left"
33+
| "slide-right"
34+
| "slide-in-bottom-left"
35+
| "slide-in-top-left"
36+
| "slide-in-top-right"
37+
| "slide-in-bottom-right";
38+
39+
const getDialogAnimationClasses = (animation: DialogAnimationType) => {
40+
const baseClasses =
41+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
42+
43+
switch (animation) {
44+
case "fade":
45+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95`;
46+
case "slide-left":
47+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2`;
48+
case "slide-right":
49+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=open]:slide-in-from-right-1/2`;
50+
case "slide-in-bottom-left":
51+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-bottom-[48%]`;
52+
case "slide-in-top-left":
53+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`;
54+
case "slide-in-top-right":
55+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-right-1/2 data-[state=open]:slide-in-from-top-[48%]`;
56+
case "slide-in-bottom-right":
57+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-right-1/2 data-[state=open]:slide-in-from-bottom-[48%]`;
58+
default:
59+
return `${baseClasses} data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2`;
60+
}
61+
};
62+
3063
const DialogContent = React.forwardRef<
3164
React.ElementRef<typeof DialogPrimitive.Content>,
32-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
33-
>(({ className, children, ...props }, ref) => (
65+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
66+
animation?: DialogAnimationType;
67+
}
68+
>(({ className, children, animation = "slide-left", ...props }, ref) => (
3469
<DialogPortal>
3570
<DialogOverlay />
3671
<DialogPrimitive.Content
3772
ref={ref}
3873
className={cn(
39-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
74+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200",
75+
getDialogAnimationClasses(animation),
76+
"sm:rounded-lg",
4077
className
4178
)}
4279
{...props}

src/data-access/modules.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ export async function reorderModules(updates: { id: number; order: number }[]) {
5858
return results;
5959
});
6060
}
61+
62+
export async function deleteModule(moduleId: number) {
63+
return database.delete(modules).where(eq(modules.id, moduleId));
64+
}

src/hooks/use-continue-slug.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ export function useContinueSlug() {
1111
const lastWatched = getLastWatchedSegment();
1212
if (lastWatched) {
1313
setCourseLink(lastWatched);
14-
} else if (firstSegment.data) {
14+
} else if (firstSegment.data?.slug) {
1515
setCourseLink(firstSegment.data.slug);
16+
} else {
17+
// If no first segment is available, set to empty string
18+
// This will be handled by the header component
19+
setCourseLink("");
1620
}
1721
};
1822

0 commit comments

Comments
 (0)