Compare commits
3 Commits
a78473b5ff
...
f35f4565b6
| Author | SHA1 | Date | |
|---|---|---|---|
| f35f4565b6 | |||
| 5cd989b098 | |||
| edab42fd26 |
12
drizzle/0008_skinny_warpath.sql
Normal file
12
drizzle/0008_skinny_warpath.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE `friendship` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`requesterId` text NOT NULL,
|
||||||
|
`addresseeId` text NOT NULL,
|
||||||
|
`status` text DEFAULT 'pending' NOT NULL,
|
||||||
|
`createdAt` integer NOT NULL,
|
||||||
|
`updatedAt` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`requesterId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`addresseeId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `friendship_requesterId_addresseeId_unique` ON `friendship` (`requesterId`,`addresseeId`);
|
||||||
1262
drizzle/meta/0008_snapshot.json
Normal file
1262
drizzle/meta/0008_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,13 @@
|
|||||||
"when": 1772735982970,
|
"when": 1772735982970,
|
||||||
"tag": "0007_gray_shinko_yamashiro",
|
"tag": "0007_gray_shinko_yamashiro",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1772821532270,
|
||||||
|
"tag": "0008_skinny_warpath",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,15 @@
|
|||||||
{#if columnVisibility.status !== false}
|
{#if columnVisibility.status !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.status === dailyCharacter.status ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.status === dailyCharacter.status ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center">
|
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center">
|
||||||
{character.status === 'Alive' ? 'Vivant' : character.status === 'Deceased' || character.status === 'Dead' ? 'Mort' : character.status || 'Inconnu'}
|
{character.status === 'Alive'
|
||||||
|
? 'Vivant'
|
||||||
|
: character.status === 'Deceased' || character.status === 'Dead'
|
||||||
|
? 'Mort'
|
||||||
|
: character.status === 'Unknown'
|
||||||
|
? 'Inconnu'
|
||||||
|
: character.status === null
|
||||||
|
? '-'
|
||||||
|
: character.status || 'Inconnu'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { user } from './auth.schema';
|
|||||||
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Smile' | 'Unknown';
|
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Smile' | 'Unknown';
|
||||||
|
|
||||||
export type Status = 'Alive' | 'Dead' | 'Unknown';
|
export type Status = 'Alive' | 'Dead' | 'Unknown';
|
||||||
|
export type FriendshipStatus = 'pending' | 'accepted' | 'declined';
|
||||||
|
|
||||||
// Define the site config table schema
|
// Define the site config table schema
|
||||||
export const config = sqliteTable('config', {
|
export const config = sqliteTable('config', {
|
||||||
@@ -122,5 +123,23 @@ export const userCharacterHistory = sqliteTable('userCharacterHistory', {
|
|||||||
unique().on(table.userId, table.characterHistoryId)
|
unique().on(table.userId, table.characterHistoryId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Define the friendship table schema (friend requests + accepted friends)
|
||||||
|
export const friendship = sqliteTable('friendship', {
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
requesterId: text('requesterId')
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
addresseeId: text('addresseeId')
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
status: text('status').$type<FriendshipStatus>().notNull().default('pending'),
|
||||||
|
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
|
||||||
|
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
|
||||||
|
}, (table) => [
|
||||||
|
unique().on(table.requesterId, table.addresseeId)
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
export * from './auth.schema';
|
export * from './auth.schema';
|
||||||
|
|||||||
@@ -167,9 +167,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status filter
|
// Status filter
|
||||||
if (characterFilters.status.length > 0 && !characterFilters.status.includes(char.status)) {
|
if (characterFilters.status.length > 0) {
|
||||||
|
const normalizedStatus = normalizeStatus(char.status);
|
||||||
|
if (!characterFilters.status.includes(normalizedStatus)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Height filter
|
// Height filter
|
||||||
if (characterFilters.hasHeight && (char.height === null || char.height === undefined)) {
|
if (characterFilters.hasHeight && (char.height === null || char.height === undefined)) {
|
||||||
@@ -357,6 +360,35 @@
|
|||||||
generateNewCharacter();
|
generateNewCharacter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeStatus(status: unknown): string {
|
||||||
|
if (status == null) {
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof status !== 'string') {
|
||||||
|
return String(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = status.trim();
|
||||||
|
if (value === '') {
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case 'alive':
|
||||||
|
return 'Alive';
|
||||||
|
case 'dead':
|
||||||
|
case 'deceased':
|
||||||
|
return 'Dead';
|
||||||
|
case 'unknown':
|
||||||
|
case 'inconnu':
|
||||||
|
case '-':
|
||||||
|
return 'Unknown';
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { fail, redirect } from '@sveltejs/kit';
|
|||||||
import type { Actions, PageServerLoad } from './$types';
|
import type { Actions, PageServerLoad } from './$types';
|
||||||
import { auth } from '$lib/server/auth';
|
import { auth } from '$lib/server/auth';
|
||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { session, userCharacterHistory, characterHistory, character } from '$lib/server/db/schema';
|
import { session, userCharacterHistory, characterHistory, character, friendship, user } from '$lib/server/db/schema';
|
||||||
import { eq, desc } from 'drizzle-orm';
|
import { and, desc, eq, or } from 'drizzle-orm';
|
||||||
import { APIError } from 'better-auth/api';
|
import { APIError } from 'better-auth/api';
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
@@ -11,6 +11,8 @@ export const load: PageServerLoad = async (event) => {
|
|||||||
return redirect(302, '/login');
|
return redirect(302, '/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentUserId = event.locals.user.id;
|
||||||
|
|
||||||
// Fetch all sessions for this user
|
// Fetch all sessions for this user
|
||||||
const userSessions = await db
|
const userSessions = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -34,10 +36,69 @@ export const load: PageServerLoad = async (event) => {
|
|||||||
.where(eq(userCharacterHistory.userId, event.locals.user.id))
|
.where(eq(userCharacterHistory.userId, event.locals.user.id))
|
||||||
.orderBy(desc(characterHistory.date));
|
.orderBy(desc(characterHistory.date));
|
||||||
|
|
||||||
|
const incomingRequests = await db
|
||||||
|
.select({
|
||||||
|
id: friendship.id,
|
||||||
|
createdAt: friendship.createdAt,
|
||||||
|
requesterId: friendship.requesterId,
|
||||||
|
requesterName: user.name,
|
||||||
|
requesterEmail: user.email,
|
||||||
|
requesterImage: user.image
|
||||||
|
})
|
||||||
|
.from(friendship)
|
||||||
|
.innerJoin(user, eq(friendship.requesterId, user.id))
|
||||||
|
.where(and(eq(friendship.addresseeId, currentUserId), eq(friendship.status, 'pending')))
|
||||||
|
.orderBy(desc(friendship.createdAt));
|
||||||
|
|
||||||
|
const outgoingRequests = await db
|
||||||
|
.select({
|
||||||
|
id: friendship.id,
|
||||||
|
createdAt: friendship.createdAt,
|
||||||
|
addresseeId: friendship.addresseeId,
|
||||||
|
addresseeName: user.name,
|
||||||
|
addresseeEmail: user.email,
|
||||||
|
addresseeImage: user.image
|
||||||
|
})
|
||||||
|
.from(friendship)
|
||||||
|
.innerJoin(user, eq(friendship.addresseeId, user.id))
|
||||||
|
.where(and(eq(friendship.requesterId, currentUserId), eq(friendship.status, 'pending')))
|
||||||
|
.orderBy(desc(friendship.createdAt));
|
||||||
|
|
||||||
|
const acceptedAsRequester = await db
|
||||||
|
.select({
|
||||||
|
id: friendship.id,
|
||||||
|
createdAt: friendship.createdAt,
|
||||||
|
friendId: friendship.addresseeId,
|
||||||
|
friendName: user.name,
|
||||||
|
friendEmail: user.email,
|
||||||
|
friendImage: user.image
|
||||||
|
})
|
||||||
|
.from(friendship)
|
||||||
|
.innerJoin(user, eq(friendship.addresseeId, user.id))
|
||||||
|
.where(and(eq(friendship.requesterId, currentUserId), eq(friendship.status, 'accepted')));
|
||||||
|
|
||||||
|
const acceptedAsAddressee = await db
|
||||||
|
.select({
|
||||||
|
id: friendship.id,
|
||||||
|
createdAt: friendship.createdAt,
|
||||||
|
friendId: friendship.requesterId,
|
||||||
|
friendName: user.name,
|
||||||
|
friendEmail: user.email,
|
||||||
|
friendImage: user.image
|
||||||
|
})
|
||||||
|
.from(friendship)
|
||||||
|
.innerJoin(user, eq(friendship.requesterId, user.id))
|
||||||
|
.where(and(eq(friendship.addresseeId, currentUserId), eq(friendship.status, 'accepted')));
|
||||||
|
|
||||||
|
const friends = [...acceptedAsRequester, ...acceptedAsAddressee].sort((a, b) => b.createdAt - a.createdAt);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: event.locals.user,
|
user: event.locals.user,
|
||||||
sessions: userSessions,
|
sessions: userSessions,
|
||||||
dailyHistory: dailyHistory
|
dailyHistory: dailyHistory,
|
||||||
|
incomingRequests,
|
||||||
|
outgoingRequests,
|
||||||
|
friends
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,5 +194,207 @@ export const actions: Actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Session révoquée avec succès' };
|
return { success: true, message: 'Session révoquée avec succès' };
|
||||||
|
},
|
||||||
|
sendFriendRequest: async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const friendEmail = formData.get('friendEmail')?.toString().trim().toLowerCase() ?? '';
|
||||||
|
|
||||||
|
if (!friendEmail) {
|
||||||
|
return fail(400, { message: 'Email requis pour envoyer une demande' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const me = event.locals.user;
|
||||||
|
if (friendEmail === me.email?.toLowerCase()) {
|
||||||
|
return fail(400, { message: 'Tu ne peux pas t\'ajouter toi-même' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [targetUser] = await db
|
||||||
|
.select({ id: user.id, email: user.email })
|
||||||
|
.from(user)
|
||||||
|
.where(eq(user.email, friendEmail))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!targetUser) {
|
||||||
|
return fail(404, { message: 'Aucun utilisateur trouvé avec cet email' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [existing] = await db
|
||||||
|
.select()
|
||||||
|
.from(friendship)
|
||||||
|
.where(
|
||||||
|
or(
|
||||||
|
and(eq(friendship.requesterId, me.id), eq(friendship.addresseeId, targetUser.id)),
|
||||||
|
and(eq(friendship.requesterId, targetUser.id), eq(friendship.addresseeId, me.id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
await db.insert(friendship).values({
|
||||||
|
requesterId: me.id,
|
||||||
|
addresseeId: targetUser.id,
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
});
|
||||||
|
return { success: true, message: 'Demande d\'ami envoyée' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.status === 'accepted') {
|
||||||
|
return fail(400, { message: 'Vous êtes déjà amis' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.status === 'pending') {
|
||||||
|
if (existing.requesterId === targetUser.id && existing.addresseeId === me.id) {
|
||||||
|
await db
|
||||||
|
.update(friendship)
|
||||||
|
.set({
|
||||||
|
status: 'accepted',
|
||||||
|
updatedAt: now
|
||||||
|
})
|
||||||
|
.where(eq(friendship.id, existing.id));
|
||||||
|
return { success: true, message: 'Demande acceptée automatiquement' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return fail(400, { message: 'Demande déjà envoyée' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(friendship)
|
||||||
|
.set({
|
||||||
|
requesterId: me.id,
|
||||||
|
addresseeId: targetUser.id,
|
||||||
|
status: 'pending',
|
||||||
|
updatedAt: now
|
||||||
|
})
|
||||||
|
.where(eq(friendship.id, existing.id));
|
||||||
|
|
||||||
|
return { success: true, message: 'Demande d\'ami envoyée' };
|
||||||
|
},
|
||||||
|
acceptFriendRequest: async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const friendshipId = formData.get('friendshipId')?.toString() ?? '';
|
||||||
|
|
||||||
|
if (!friendshipId) {
|
||||||
|
return fail(400, { message: 'Demande invalide' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const result = await db
|
||||||
|
.update(friendship)
|
||||||
|
.set({ status: 'accepted', updatedAt: now })
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(friendship.id, friendshipId),
|
||||||
|
eq(friendship.addresseeId, event.locals.user.id),
|
||||||
|
eq(friendship.status, 'pending')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({ id: friendship.id });
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return fail(404, { message: 'Demande introuvable' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: 'Demande acceptée' };
|
||||||
|
},
|
||||||
|
declineFriendRequest: async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const friendshipId = formData.get('friendshipId')?.toString() ?? '';
|
||||||
|
|
||||||
|
if (!friendshipId) {
|
||||||
|
return fail(400, { message: 'Demande invalide' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const result = await db
|
||||||
|
.update(friendship)
|
||||||
|
.set({ status: 'declined', updatedAt: now })
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(friendship.id, friendshipId),
|
||||||
|
eq(friendship.addresseeId, event.locals.user.id),
|
||||||
|
eq(friendship.status, 'pending')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({ id: friendship.id });
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return fail(404, { message: 'Demande introuvable' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: 'Demande refusée' };
|
||||||
|
},
|
||||||
|
cancelFriendRequest: async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const friendshipId = formData.get('friendshipId')?.toString() ?? '';
|
||||||
|
|
||||||
|
if (!friendshipId) {
|
||||||
|
return fail(400, { message: 'Demande invalide' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.delete(friendship)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(friendship.id, friendshipId),
|
||||||
|
eq(friendship.requesterId, event.locals.user.id),
|
||||||
|
eq(friendship.status, 'pending')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({ id: friendship.id });
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return fail(404, { message: 'Demande introuvable' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: 'Demande annulée' };
|
||||||
|
},
|
||||||
|
removeFriend: async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const friendshipId = formData.get('friendshipId')?.toString() ?? '';
|
||||||
|
|
||||||
|
if (!friendshipId) {
|
||||||
|
return fail(400, { message: 'Relation invalide' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.delete(friendship)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(friendship.id, friendshipId),
|
||||||
|
eq(friendship.status, 'accepted'),
|
||||||
|
or(eq(friendship.requesterId, event.locals.user.id), eq(friendship.addresseeId, event.locals.user.id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({ id: friendship.id });
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return fail(404, { message: 'Relation introuvable' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: 'Ami supprimé' };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,14 +10,18 @@
|
|||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let activeTab = $state<'profile' | 'password' | 'sessions' | 'daily'>('profile');
|
let activeTab = $state<'profile' | 'password' | 'sessions' | 'daily' | 'friends'>('profile');
|
||||||
let name = $state('');
|
let name = $state('');
|
||||||
|
let friendEmail = $state('');
|
||||||
let showSuccess = $state(false);
|
let showSuccess = $state(false);
|
||||||
let oldPassword = $state('');
|
let oldPassword = $state('');
|
||||||
let newPassword = $state('');
|
let newPassword = $state('');
|
||||||
let confirmPassword = $state('');
|
let confirmPassword = $state('');
|
||||||
let sessions = $state<any[]>([]);
|
let sessions = $state<any[]>([]);
|
||||||
let dailyHistory = $state<any[]>([]);
|
let dailyHistory = $state<any[]>([]);
|
||||||
|
let friends = $state<any[]>([]);
|
||||||
|
let incomingRequests = $state<any[]>([]);
|
||||||
|
let outgoingRequests = $state<any[]>([]);
|
||||||
let tabsElement: HTMLDivElement | undefined;
|
let tabsElement: HTMLDivElement | undefined;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -32,6 +36,18 @@
|
|||||||
dailyHistory = (data as any).dailyHistory || [];
|
dailyHistory = (data as any).dailyHistory || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
friends = (data as any).friends || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
incomingRequests = (data as any).incomingRequests || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
outgoingRequests = (data as any).outgoingRequests || [];
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (form && form.success === true) {
|
if (form && form.success === true) {
|
||||||
showSuccess = true;
|
showSuccess = true;
|
||||||
@@ -41,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleTabChange = (tab: 'profile' | 'password' | 'sessions' | 'daily') => {
|
const handleTabChange = (tab: 'profile' | 'password' | 'sessions' | 'daily' | 'friends') => {
|
||||||
activeTab = tab;
|
activeTab = tab;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,6 +120,14 @@
|
|||||||
>
|
>
|
||||||
Sessions
|
Sessions
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={() => handleTabChange('friends')}
|
||||||
|
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'friends'
|
||||||
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
|
>
|
||||||
|
Amis
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
@@ -184,6 +208,128 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Friends Tab -->
|
||||||
|
{#if activeTab === 'friends'}
|
||||||
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
|
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
||||||
|
Système d'amis
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/sendFriendRequest"
|
||||||
|
use:enhance={() => {
|
||||||
|
isLoading = true;
|
||||||
|
return async ({ update }) => {
|
||||||
|
isLoading = false;
|
||||||
|
friendEmail = '';
|
||||||
|
await update();
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
class="mb-8 space-y-3"
|
||||||
|
>
|
||||||
|
<label for="friendEmail" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
|
Ajouter un ami par email
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input
|
||||||
|
id="friendEmail"
|
||||||
|
type="email"
|
||||||
|
name="friendEmail"
|
||||||
|
required
|
||||||
|
bind:value={friendEmail}
|
||||||
|
placeholder="ami@email.com"
|
||||||
|
class="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
class="rounded-full bg-amber-300 px-4 py-2 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
||||||
|
>
|
||||||
|
{isLoading ? 'Envoi...' : 'Envoyer'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{#if form?.message}
|
||||||
|
<p class="text-sm {form.success ? 'text-green-300' : 'text-red-300'}">{form.message}</p>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="space-y-8">
|
||||||
|
<div>
|
||||||
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes reçues</h3>
|
||||||
|
{#if incomingRequests.length === 0}
|
||||||
|
<p class="text-sm text-slate-400">Aucune demande reçue.</p>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#each incomingRequests as req}
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-white">{req.requesterName}</p>
|
||||||
|
<p class="text-xs text-slate-400">{req.requesterEmail}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<form method="POST" action="?/acceptFriendRequest" use:enhance>
|
||||||
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
|
<button type="submit" class="rounded-lg border border-emerald-400/50 bg-emerald-900/20 px-3 py-1.5 text-xs font-semibold text-emerald-300 transition hover:bg-emerald-900/40">Accepter</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="?/declineFriendRequest" use:enhance>
|
||||||
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Refuser</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes envoyées</h3>
|
||||||
|
{#if outgoingRequests.length === 0}
|
||||||
|
<p class="text-sm text-slate-400">Aucune demande envoyée.</p>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#each outgoingRequests as req}
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-white">{req.addresseeName}</p>
|
||||||
|
<p class="text-xs text-slate-400">{req.addresseeEmail}</p>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="?/cancelFriendRequest" use:enhance>
|
||||||
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Annuler</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Mes amis</h3>
|
||||||
|
{#if friends.length === 0}
|
||||||
|
<p class="text-sm text-slate-400">Tu n'as pas encore d'amis.</p>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#each friends as friend}
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-white">{friend.friendName}</p>
|
||||||
|
<p class="text-xs text-slate-400">{friend.friendEmail}</p>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="?/removeFriend" use:enhance>
|
||||||
|
<input type="hidden" name="friendshipId" value={friend.id} />
|
||||||
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Supprimer</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Password Tab -->
|
<!-- Password Tab -->
|
||||||
{#if activeTab === 'password'}
|
{#if activeTab === 'password'}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user