Skip to content

Commit 8bec39f

Browse files
authored
fix: auto join tenant (#220)
1 parent 89fa116 commit 8bec39f

File tree

17 files changed

+559
-89
lines changed

17 files changed

+559
-89
lines changed

public/locales/en/auth.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,8 @@
101101
"updatePassword": "Update Password",
102102
"resetPasswordSuccess": "Password Reset Successful",
103103
"passwordResetSuccessfully": "Your password has been reset successfully.",
104-
"resetPasswordErrorDesc": "An error occurred while resetting your password."
104+
"resetPasswordErrorDesc": "An error occurred while resetting your password.",
105+
"joiningChurch": "Joining church...",
106+
"joinedChurch": "Successfully joined!",
107+
"joinedChurchDesc": "Welcome to {{tenantName}}!"
105108
}

public/locales/en/events.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"startsAt": "Starts at",
4949
"endsAt": "Ends at",
5050
"eventLinkText": "Event Link",
51+
"addToGoogleCalendar": "Add to Google Calendar",
5152
"private": "Private",
5253
"fullDay": "Full Day"
5354
}

public/locales/en/services.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@
6262
"subtitle": "Subtitle",
6363
"servicePersonnel": "Service Personnel",
6464
"actions": "Actions",
65+
"addToGoogleCalendar": "Add to Google Calendar",
66+
"emptyValue": "",
67+
"email": "Email",
68+
"notProvided": "Not provided",
69+
"unknownUser": "Unknown user",
70+
"unknownRole": "Unknown role",
71+
"noAssignees": "Unassigned",
72+
"loadServicePersonnelError": "Unable to load service personnel.",
73+
"confirmDeleteScheduleTitle": "Confirm delete service schedule",
74+
"confirmDeleteScheduleDescription": "Are you sure you want to delete the {{date}} {{serviceName}} service schedule? This action cannot be undone.",
6575
"addServiceRoles": "Add Service Roles",
6676
"roleName": "Role Name",
6777
"roleNameRequired": "Role name is required",

public/locales/zh-TW/auth.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,8 @@
100100
"updatePassword": "更新密碼",
101101
"resetPasswordSuccess": "密碼重設成功",
102102
"passwordResetSuccessfully": "您的密碼已成功重設。",
103-
"resetPasswordErrorDesc": "重設密碼時發生錯誤。"
103+
"resetPasswordErrorDesc": "重設密碼時發生錯誤。",
104+
"joiningChurch": "正在加入教會...",
105+
"joinedChurch": "成功加入!",
106+
"joinedChurchDesc": "歡迎來到{{tenantName}}!"
104107
}

public/locales/zh-TW/events.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"startsAt": "開始於",
4949
"endsAt": "結束於",
5050
"eventLinkText": "活動連結",
51+
"addToGoogleCalendar": "加入 Google 日曆",
5152
"private": "私人",
5253
"fullDay": "全天"
5354
}

public/locales/zh-TW/services.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@
6262
"subtitle": "副標題",
6363
"servicePersonnel": "服事人員",
6464
"actions": "操作",
65+
"addToGoogleCalendar": "加入 Google 日曆",
66+
"emptyValue": "",
67+
"email": "電子郵件",
68+
"notProvided": "未提供",
69+
"unknownUser": "未知用戶",
70+
"unknownRole": "未知角色",
71+
"noAssignees": "未指派",
72+
"loadServicePersonnelError": "無法載入服事人員資料",
73+
"confirmDeleteScheduleTitle": "確認刪除服事排班",
74+
"confirmDeleteScheduleDescription": "您確定要刪除 {{date}} 的 {{serviceName}} 服事排班嗎?此操作無法撤銷。",
6575
"addServiceRoles": "新增服事角色",
6676
"roleName": "角色名稱",
6777
"roleNameRequired": "角色名稱為必填",

src/components/Auth/GoogleOAuthButton.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@ export function GoogleOAuthButton({ onSuccess: _onSuccess, onError }: GoogleOAut
2121
const handleGoogleSignIn = async () => {
2222
setIsLoading(true);
2323
try {
24-
// Get current redirect parameter if exists
24+
// Get current flow and redirect parameters
2525
const searchParams = new URLSearchParams(window.location.search);
26+
const flowParam = searchParams.get("flow");
2627
const redirectParam = searchParams.get("redirect");
2728

2829
// Build auth redirect URL
2930
const authPath = slug ? `/tenant/${slug}/auth` : "/auth";
3031
let redirectTo = window.location.origin + authPath;
3132

32-
// Append redirect parameter if it exists
33+
// Preserve both flow and redirect parameters
34+
const queryParams = new URLSearchParams();
35+
if (flowParam) queryParams.set("flow", flowParam);
3336
if (redirectParam && slug && redirectParam.startsWith(`/tenant/${slug}`)) {
34-
redirectTo += `?redirect=${encodeURIComponent(redirectParam)}`;
37+
queryParams.set("redirect", redirectParam);
38+
}
39+
40+
if (queryParams.toString()) {
41+
redirectTo += `?${queryParams.toString()}`;
3542
}
3643

3744
const { data: _data, error } = await supabase.auth.signInWithOAuth({

src/components/Events/EventActions.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface EventActionsProps {
1818
onDeleteEvent: (eventId: string) => Promise<void>;
1919
onCopyEvent?: (event: EventWithGroups) => void;
2020
allGroups: Group[];
21+
className?: string;
2122
}
2223

2324
export function EventActions({
@@ -26,6 +27,7 @@ export function EventActions({
2627
onDeleteEvent,
2728
onCopyEvent,
2829
allGroups,
30+
className,
2931
}: EventActionsProps) {
3032
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
3133
const [isDeleting, setIsDeleting] = React.useState(false);
@@ -74,7 +76,7 @@ export function EventActions({
7476
};
7577

7678
return (
77-
<div className="absolute top-4 right-4">
79+
<div className={className}>
7880
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
7981
<DropdownMenuTrigger asChild>
8082
<Button variant="ghost" size="icon" className="h-8 w-8">

src/components/Events/EventCard.tsx

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { Badge } from "@/components/ui/badge";
1515
import { EventActions } from "./EventActions";
1616
import { cn } from "@/lib/utils";
1717
import { useTranslation } from "react-i18next";
18+
import { AddToGoogleCalendarLink } from "@/components/shared/AddToGoogleCalendarLink";
19+
import { combineDateAndTimeLocal, parseISODateLocal } from "@/lib/googleCalendar";
1820

1921
interface EventCardProps {
2022
event: EventWithGroups;
@@ -46,29 +48,60 @@ export function EventCard({
4648
timeDisplay = `${t("events:endsAt")} ${event.end_time}`;
4749
}
4850

51+
const calendarLabel = t("events:addToGoogleCalendar");
52+
const hasAnyTime = !!event.start_time || !!event.end_time;
53+
const calendarStart = hasAnyTime
54+
? ((event.start_time && combineDateAndTimeLocal(event.date, event.start_time)) ??
55+
parseISODateLocal(event.date) ??
56+
new Date())
57+
: (parseISODateLocal(event.date) ?? new Date());
58+
const calendarEnd = hasAnyTime
59+
? ((event.end_time && combineDateAndTimeLocal(event.date, event.end_time)) ??
60+
new Date(calendarStart.getTime() + 60 * 60 * 1000))
61+
: calendarStart;
62+
const calendarDetails = [
63+
event.description ?? "",
64+
event.event_link ? `\n\n${event.event_link}` : "",
65+
].join("");
66+
4967
return (
5068
<Card className={cn("relative", event.visibility === "private" && "border-primary/30")}>
51-
{isEditable && (
52-
<EventActions
53-
event={event}
54-
onEventUpdated={onEventUpdated}
55-
onDeleteEvent={onDeleteEvent}
56-
onCopyEvent={onCopyEvent}
57-
allGroups={allGroups}
58-
/>
59-
)}
60-
6169
<CardHeader>
62-
<CardTitle>{event.name}</CardTitle>
63-
<CardDescription className="flex items-center gap-1">
64-
<Calendar className="h-4 w-4" /> {formattedDate}
65-
{hasTime && (
66-
<>
67-
<span className="mx-1"></span>
68-
<Clock className="h-4 w-4" /> {timeDisplay}
69-
</>
70-
)}
71-
</CardDescription>
70+
<div className="flex items-start justify-between gap-3">
71+
<div className="min-w-0">
72+
<CardTitle className="truncate">{event.name}</CardTitle>
73+
<CardDescription className="flex items-center gap-1">
74+
<Calendar className="h-4 w-4" /> {formattedDate}
75+
{hasTime && (
76+
<>
77+
<span className="mx-1"></span>
78+
<Clock className="h-4 w-4" /> {timeDisplay}
79+
</>
80+
)}
81+
</CardDescription>
82+
</div>
83+
<div className="flex shrink-0 items-center gap-2">
84+
<AddToGoogleCalendarLink
85+
title={event.name}
86+
start={calendarStart}
87+
end={calendarEnd}
88+
isAllDay={!hasAnyTime}
89+
details={calendarDetails}
90+
label={calendarLabel}
91+
className="px-0"
92+
/>
93+
{isEditable && (
94+
<EventActions
95+
className="shrink-0"
96+
event={event}
97+
onEventUpdated={onEventUpdated}
98+
onDeleteEvent={onDeleteEvent}
99+
onCopyEvent={onCopyEvent}
100+
allGroups={allGroups}
101+
/>
102+
)}
103+
</div>
104+
</div>
72105
</CardHeader>
73106

74107
{event.description && (
@@ -77,36 +110,34 @@ export function EventCard({
77110
</CardContent>
78111
)}
79112

80-
{(event.event_link || (event.groups && event.groups.length > 0)) && (
81-
<CardFooter className="flex flex-wrap gap-2">
82-
{event.event_link && (
83-
<a
84-
href={event.event_link}
85-
target="_blank"
86-
rel="noopener noreferrer"
87-
className="text-sm text-primary underline hover:no-underline"
88-
>
89-
{t("events:eventLinkText")}
90-
</a>
91-
)}
92-
93-
{event.groups &&
94-
event.groups.length > 0 &&
95-
event.groups.map((group) =>
96-
group ? (
97-
<Badge key={group.id} variant="outline">
98-
{group.name}
99-
</Badge>
100-
) : null,
101-
)}
113+
<CardFooter className="flex flex-wrap gap-2">
114+
{event.event_link && (
115+
<a
116+
href={event.event_link}
117+
target="_blank"
118+
rel="noopener noreferrer"
119+
className="text-sm text-primary underline hover:no-underline"
120+
>
121+
{t("events:eventLinkText")}
122+
</a>
123+
)}
102124

103-
{event.visibility === "private" && (
104-
<Badge variant="secondary" className="ml-auto">
105-
{t("events:private")}
106-
</Badge>
125+
{event.groups &&
126+
event.groups.length > 0 &&
127+
event.groups.map((group) =>
128+
group ? (
129+
<Badge key={group.id} variant="outline">
130+
{group.name}
131+
</Badge>
132+
) : null,
107133
)}
108-
</CardFooter>
109-
)}
134+
135+
{event.visibility === "private" && (
136+
<Badge variant="secondary" className="ml-auto">
137+
{t("events:private")}
138+
</Badge>
139+
)}
140+
</CardFooter>
110141
</Card>
111142
);
112143
}

0 commit comments

Comments
 (0)