Skip to content

Commit 0fb8312

Browse files
okey c bon, mais dans le profil ajoute l'avatar source (genre la il et e
1 parent 8fb47f1 commit 0fb8312

File tree

3 files changed

+124
-9
lines changed

3 files changed

+124
-9
lines changed

src/app/(app)/profile/page.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Label } from '@/components/ui/label';
1212
import { useForm } from 'react-hook-form';
1313
import { zodResolver } from '@hookform/resolvers/zod';
1414
import * as z from 'zod';
15-
import { useEffect, useState, useActionState, startTransition } from 'react'; // Corrected import
15+
import { useEffect, useState, useActionState, startTransition } from 'react';
1616
import { useToast } from '@/hooks/use-toast';
1717
import * as authService from '@/lib/authService';
1818
import { fetchUserGithubOAuthTokenAction, disconnectGithubAction, fetchGithubUserDetailsAction, fetchDiscordUserDetailsAction, disconnectDiscordAction } from '@/app/(app)/projects/[id]/actions';
@@ -43,6 +43,8 @@ interface DiscordUserDetails {
4343
discriminator: string;
4444
}
4545

46+
type AvatarSource = 'flowup' | 'github' | 'discord';
47+
4648

4749
export default function ProfilePage() {
4850
const { user, isLoading: authLoading, refreshUser } = useAuth();
@@ -57,6 +59,8 @@ export default function ProfilePage() {
5759

5860
const [discordUserDetails, setDiscordUserDetails] = useState<DiscordUserDetails | null>(null);
5961
const [isLoadingDiscord, setIsLoadingDiscord] = useState(true);
62+
63+
const [avatarSource, setAvatarSource] = useState<AvatarSource>('flowup');
6064

6165
const [disconnectGithubState, disconnectGithubFormAction, isDisconnectGithubPending] = useActionState(disconnectGithubAction, { success: false });
6266
const [disconnectDiscordState, disconnectDiscordFormAction, isDisconnectDiscordPending] = useActionState(disconnectDiscordAction, { success: false });
@@ -106,6 +110,18 @@ export default function ProfilePage() {
106110
if (!authLoading) loadExternalData();
107111
}, [user, authLoading]);
108112

113+
useEffect(() => {
114+
if (user && !isLoadingGithub && !isLoadingDiscord) {
115+
let source: AvatarSource = 'flowup';
116+
if (githubUserDetails?.avatar_url && user.avatar === githubUserDetails.avatar_url) {
117+
source = 'github';
118+
} else if (discordUserDetails?.avatar && user.avatar === `https://cdn.discordapp.com/avatars/${discordUserDetails.id}/${discordUserDetails.avatar}.png`) {
119+
source = 'discord';
120+
}
121+
setAvatarSource(source);
122+
}
123+
}, [user, githubUserDetails, discordUserDetails, isLoadingGithub, isLoadingDiscord]);
124+
109125
useEffect(() => {
110126
if (!isDisconnectGithubPending && disconnectGithubState) {
111127
if (disconnectGithubState.success && disconnectGithubState.message) {
@@ -195,6 +211,15 @@ export default function ProfilePage() {
195211
}
196212
};
197213

214+
const handleAvatarSourceChange = (source: AvatarSource) => {
215+
setAvatarSource(source);
216+
if (source === 'github' && githubUserDetails?.avatar_url) {
217+
form.setValue('avatar', githubUserDetails.avatar_url, { shouldDirty: true });
218+
} else if (source === 'discord' && discordUserDetails?.avatar) {
219+
form.setValue('avatar', `https://cdn.discordapp.com/avatars/${discordUserDetails.id}/${discordUserDetails.avatar}.png`, { shouldDirty: true });
220+
}
221+
};
222+
198223
const handleConnectGitHub = () => {
199224
window.location.href = `/api/auth/github/oauth/login?redirectTo=/profile`;
200225
};
@@ -265,13 +290,13 @@ export default function ProfilePage() {
265290
<Label htmlFor="avatar">Avatar URL</Label>
266291
<div className="relative">
267292
<ImageIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
268-
<Input id="avatar" placeholder="https://example.com/avatar.png" {...form.register("avatar")} className="pl-10" />
293+
<Input id="avatar" placeholder="https://example.com/avatar.png" {...form.register("avatar")} className="pl-10" onFocus={() => setAvatarSource('flowup')} />
269294
</div>
270295
{form.formState.errors.avatar && <p className="text-sm text-destructive">{form.formState.errors.avatar.message}</p>}
271296
</div>
272297
<Card className="p-4 bg-muted/30">
273-
<h4 className="font-semibold mb-2">Avatar Source (Coming Soon)</h4>
274-
<RadioGroup defaultValue="flowup" disabled>
298+
<h4 className="font-semibold mb-2">Avatar Source</h4>
299+
<RadioGroup value={avatarSource} onValueChange={handleAvatarSourceChange}>
275300
<div className="flex items-center space-x-2">
276301
<RadioGroupItem value="flowup" id="r-flowup" />
277302
<Label htmlFor="r-flowup">Use FlowUp Avatar URL</Label>
@@ -419,3 +444,5 @@ export default function ProfilePage() {
419444
</div>
420445
);
421446
}
447+
448+

src/app/(app)/projects/[id]/actions.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ import {
3838
getTaskByUuid as dbGetTaskByUuid,
3939
updateProjectDiscordSettings as dbUpdateProjectDiscordSettings,
4040
deleteProject as dbDeleteProject,
41+
updateProjectWebhookDetails as dbUpdateProjectWebhookDetails,
4142
} from '@/lib/db';
4243
import { z } from 'zod';
4344
import { auth } from '@/lib/authEdge';
4445
import { Octokit } from 'octokit';
4546
import { Buffer } from 'buffer';
47+
import crypto from 'crypto';
4648
import { generateProjectScaffold, type GenerateProjectScaffoldInput, type GenerateProjectScaffoldOutput } from '@/ai/flows/generate-project-scaffold';
4749
import { editFileContentWithAI, type EditFileContentAIInput, type EditFileContentAIOutput } from '@/ai/flows/edit-file-content-ai';
4850
import { sendDiscordNotification } from '@/lib/discord';
@@ -1975,7 +1977,7 @@ export async function updateProjectDiscordSettingsAction(
19751977
return { error: "You do not have permission to change Discord settings for this project." };
19761978
}
19771979

1978-
const updatedProject = await dbUpdateProjectDiscordSettings(projectUuid, discordWebhookUrl, discordNotificationsEnabled, notifyTasks, notifyMembers, notifyAnnouncements);
1980+
const updatedProject = await dbUpdateProjectDiscordSettings(projectUuid, discordWebhookUrl, discordNotificationsEnabled, discordNotifyTasks, discordNotifyMembers, discordNotifyAnnouncements);
19791981

19801982
if (!updatedProject) {
19811983
return { error: "Failed to update project settings in the database." };
@@ -2024,3 +2026,87 @@ export async function deleteProjectAction(
20242026
return { error: error.message || "An unexpected error occurred." };
20252027
}
20262028
}
2029+
2030+
export interface SetupGithubWebhookFormState {
2031+
success?: boolean;
2032+
message?: string;
2033+
error?: string;
2034+
project?: Project;
2035+
}
2036+
2037+
export async function setupGithubWebhookAction(
2038+
prevState: SetupGithubWebhookFormState,
2039+
formData: FormData
2040+
): Promise<SetupGithubWebhookFormState> {
2041+
const session = await auth();
2042+
if (!session?.user?.uuid) {
2043+
return { error: "Authentication required." };
2044+
}
2045+
2046+
const projectUuid = formData.get('projectUuid') as string;
2047+
if (!projectUuid) {
2048+
return { error: "Project UUID is required." };
2049+
}
2050+
2051+
try {
2052+
const userRole = await dbGetProjectMemberRole(projectUuid, session.user.uuid);
2053+
if (!userRole || !['owner', 'co-owner'].includes(userRole)) {
2054+
return { error: "You do not have permission to set up webhooks for this project." };
2055+
}
2056+
2057+
const project = await dbGetProjectByUuid(projectUuid);
2058+
if (!project || !project.githubRepoName) {
2059+
return { error: "Project not found or not linked to GitHub." };
2060+
}
2061+
if (project.githubWebhookId) {
2062+
return { error: "A webhook is already configured for this project." };
2063+
}
2064+
2065+
const oauthToken = await dbGetUserGithubOAuthToken(session.user.uuid);
2066+
if (!oauthToken?.accessToken) {
2067+
return { error: "GitHub OAuth token not found. Please connect your account." };
2068+
}
2069+
2070+
const [owner, repo] = project.githubRepoName.split('/');
2071+
if (!owner || !repo) return { error: "Invalid GitHub repository name format." };
2072+
2073+
const octokit = new Octokit({ auth: oauthToken.accessToken });
2074+
const webhookSecret = crypto.randomBytes(20).toString('hex');
2075+
const webhookUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/github`;
2076+
2077+
const { data: newWebhook } = await octokit.rest.repos.createWebhook({
2078+
owner,
2079+
repo,
2080+
config: {
2081+
url: webhookUrl,
2082+
content_type: 'json',
2083+
secret: webhookSecret,
2084+
},
2085+
events: ['push', 'pull_request', 'issues'],
2086+
active: true,
2087+
});
2088+
2089+
const updatedProject = await dbUpdateProjectWebhookDetails(project.uuid, newWebhook.id, webhookSecret);
2090+
if (!updatedProject) {
2091+
return { error: "Webhook created on GitHub, but failed to save details in FlowUp." };
2092+
}
2093+
2094+
return { success: true, message: `Webhook (ID: ${newWebhook.id}) successfully created on GitHub!`, project: updatedProject };
2095+
2096+
} catch (error: any) {
2097+
console.error("Error setting up GitHub webhook:", error);
2098+
let errorMessage = error.message || "An unexpected error occurred.";
2099+
if (error.status === 422) { // Unprocessable Entity
2100+
if(error.message.includes("Hook already exists")) {
2101+
errorMessage = "A webhook for this URL already exists on the repository. Please check your repository settings on GitHub.";
2102+
} else {
2103+
errorMessage = `Could not create webhook (422): ${error.message}. Check permissions and configuration.`;
2104+
}
2105+
} else if (error.status === 404) {
2106+
errorMessage = "Repository not found. Check that the linked repository exists and you have access.";
2107+
}
2108+
return { error: errorMessage };
2109+
}
2110+
}
2111+
2112+

src/lib/db.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -779,19 +779,19 @@ export async function updateProjectDiscordSettings(
779779
const now = new Date().toISOString();
780780

781781
let setClauses = 'discordWebhookUrl = ?, discordNotificationsEnabled = ?, updatedAt = ?';
782-
let params: (string | number | boolean | null | undefined)[] = [webhookUrl, enabled, now];
782+
let params: (string | number | boolean | null | undefined)[] = [webhookUrl, enabled ? 1 : 0, now];
783783

784784
if (notifyTasks !== undefined) {
785785
setClauses += ', discordNotifyTasks = ?';
786-
params.push(notifyTasks);
786+
params.push(notifyTasks ? 1 : 0);
787787
}
788788
if (notifyMembers !== undefined) {
789789
setClauses += ', discordNotifyMembers = ?';
790-
params.push(notifyMembers);
790+
params.push(notifyMembers ? 1 : 0);
791791
}
792792
if (notifyAnnouncements !== undefined) {
793793
setClauses += ', discordNotifyAnnouncements = ?';
794-
params.push(notifyAnnouncements);
794+
params.push(notifyAnnouncements ? 1 : 0);
795795
}
796796
params.push(projectUuid);
797797

@@ -1328,3 +1328,5 @@ export async function deleteProjectAnnouncement(announcementUuid: string): Promi
13281328
const result = await connection.run('DELETE FROM project_announcements WHERE uuid = ?', announcementUuid);
13291329
return result.changes ? result.changes > 0 : false;
13301330
}
1331+
1332+

0 commit comments

Comments
 (0)