diff --git a/client/src/components/layout/StickyHeader.tsx b/client/src/components/layout/StickyHeader.tsx
index 8d7ad35..6434710 100644
--- a/client/src/components/layout/StickyHeader.tsx
+++ b/client/src/components/layout/StickyHeader.tsx
@@ -39,8 +39,11 @@ export function StickyHeader({ visible, query }: StickyHeaderProps) {
-
-
+
+ {/* søkefeltet krymper så knappene alltid får plass */}
+
+
+
diff --git a/client/src/components/profile/PreferencesForm.tsx b/client/src/components/profile/PreferencesForm.tsx
index 03e8d32..48fa3cf 100644
--- a/client/src/components/profile/PreferencesForm.tsx
+++ b/client/src/components/profile/PreferencesForm.tsx
@@ -1,4 +1,7 @@
import { useState } from 'react';
+import { useMutation } from '@apollo/client/react';
+import { useCurrentUser } from '../../lib/useCurrentUser';
+import { UPDATE_PREFERENCES } from '../../graphql/auth';
// allergiene brukeren kan velge
const ALLERGIES = ['Gluten', 'Laktose', 'Nøtter', 'Egg', 'Skalldyr', 'Soya'];
@@ -48,19 +51,55 @@ function RadioGroup({
// språk, måleenheter og allergier
export function PreferencesForm() {
- const [language, setLanguage] = useState('nb');
- const [units, setUnits] = useState('metric');
- const [allergies, setAllergies] = useState
([]);
+ const { user } = useCurrentUser();
+ const [language, setLanguage] = useState(user?.languagePref ?? 'nb');
+ const [units, setUnits] = useState(user?.unitSystem ?? 'metric');
+ const [allergies, setAllergies] = useState(user?.allergens ?? []);
- // legger til allergien hvis den ikke er valgt
- function toggleAllergy(name: string) {
- if (allergies.includes(name)) {
- setAllergies(allergies.filter((allergy) => allergy !== name));
- } else {
- setAllergies([...allergies, name]);
+ const [updatePreferences, { error }] = useMutation(UPDATE_PREFERENCES);
+ // om siste endring ble lagret
+ const [saved, setSaved] = useState(false);
+
+ // lagrer alle valgene hver gang noe endres
+ async function save(
+ nextLanguage: string,
+ nextUnits: string,
+ nextAllergies: string[],
+ ) {
+ setSaved(false);
+ try {
+ await updatePreferences({
+ variables: {
+ languagePref: nextLanguage,
+ unitSystem: nextUnits,
+ allergens: nextAllergies,
+ },
+ });
+ setSaved(true);
+ } catch {
+ // feilmeldingen vises nederst i skjemaet
}
}
+ function changeLanguage(value: string) {
+ setLanguage(value);
+ save(value, units, allergies);
+ }
+
+ function changeUnits(value: string) {
+ setUnits(value);
+ save(language, value, allergies);
+ }
+
+ // legger til allergien hvis den ikke er valgt
+ function toggleAllergy(name: string) {
+ const next = allergies.includes(name)
+ ? allergies.filter((allergy) => allergy !== name)
+ : [...allergies, name];
+ setAllergies(next);
+ save(language, units, next);
+ }
+
return (
+ {/* sier om valgene ble lagret også til skjermleser */}
+
+ {error ? 'Kunne ikke lagre. Prøv igjen.' : saved ? 'Lagret' : ''}
+
);
}
diff --git a/client/src/graphql/auth.ts b/client/src/graphql/auth.ts
index 3f04d88..abdd6fb 100644
--- a/client/src/graphql/auth.ts
+++ b/client/src/graphql/auth.ts
@@ -7,6 +7,7 @@ export type BackendUser = {
languagePref: string;
unitSystem: string;
isGuest: boolean;
+ allergens: string[];
};
const USER_FIELDS = gql`
@@ -17,6 +18,7 @@ const USER_FIELDS = gql`
languagePref
unitSystem
isGuest
+ allergens
}
`;
@@ -61,3 +63,25 @@ export const CONTINUE_AS_GUEST = gql`
}
${USER_FIELDS}
`;
+
+
+// lagrer språk, måleenheter og allergier
+export const UPDATE_PREFERENCES: TypedDocumentNode<
+ { updatePreferences: BackendUser },
+ { languagePref: string; unitSystem: string; allergens: string[] }
+> = gql`
+ mutation UpdatePreferences(
+ $languagePref: String!
+ $unitSystem: String!
+ $allergens: [String!]!
+ ) {
+ updatePreferences(
+ languagePref: $languagePref
+ unitSystem: $unitSystem
+ allergens: $allergens
+ ) {
+ ...UserFields
+ }
+ }
+ ${USER_FIELDS}
+`;
diff --git a/client/src/types/user.ts b/client/src/types/user.ts
index b45fd61..ac430b1 100644
--- a/client/src/types/user.ts
+++ b/client/src/types/user.ts
@@ -7,4 +7,5 @@ export type User = {
username?: string;
languagePref?: string;
unitSystem?: string;
+ allergens?: string[];
};
diff --git a/server/src/index.ts b/server/src/index.ts
index 180b488..f9799c0 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -18,6 +18,7 @@ const typeDefs = ['recipe.graphql', 'user.graphql'].map((file) =>
const resolvers = {
Query: { ...recipeResolvers.Query, ...userResolvers.Query },
Mutation: { ...recipeResolvers.Mutation, ...userResolvers.Mutation },
+ User: userResolvers.User,
};
// for å teste med Apollo Sandbox
diff --git a/server/src/schema/resolvers/user.ts b/server/src/schema/resolvers/user.ts
index edd70d4..da34af4 100644
--- a/server/src/schema/resolvers/user.ts
+++ b/server/src/schema/resolvers/user.ts
@@ -13,6 +13,17 @@ const MIN_PASSWORD_LENGTH = 6;
type Credentials = { username: string; password: string };
+
+type Preferences = {
+ languagePref: string;
+ unitSystem: string;
+ allergens: string[];
+};
+
+// valg
+const LANGUAGES = ['nb', 'en'];
+const UNIT_SYSTEMS = ['metric', 'imperial'];
+
export const userResolvers = {
Query: {
// innlogget bruker og null hvis ingen er logget inn
@@ -107,5 +118,59 @@ export const userResolvers = {
setLoginCookie(context.res, guest.id, true);
return guest;
},
+
+ // lagrer preferansene til innlogget bruker
+ updatePreferences: async (
+ _: unknown,
+ args: Preferences,
+ context: Context,
+ ) => {
+ if (!context.userId) {
+ throw new GraphQLError('Du må være logget inn.', {
+ extensions: { code: 'UNAUTHENTICATED' },
+ });
+ }
+
+ // sjekker på nytt
+ if (
+ !LANGUAGES.includes(args.languagePref) ||
+ !UNIT_SYSTEMS.includes(args.unitSystem)
+ ) {
+ throw new GraphQLError('Ugyldig preferanse.', {
+ extensions: { code: 'BAD_USER_INPUT' },
+ });
+ }
+
+ // fjerner de gamle allergiene først
+ await prisma.user.update({
+ where: { id: context.userId },
+ data: { allergens: { set: [] } },
+ });
+
+ // lagrer de nye valgene og lager allergien hvis den ikke finnes
+ return prisma.user.update({
+ where: { id: context.userId },
+ data: {
+ languagePref: args.languagePref,
+ unitSystem: args.unitSystem,
+ allergens: {
+ connectOrCreate: args.allergens.map((name) => ({
+ where: { name },
+ create: { name },
+ })),
+ },
+ },
+ });
+ },
+ },
+
+ User: {
+ // henter navnene på allergiene til brukeren
+ allergens: async (user: { id: string }) => {
+ const allergens = await prisma.allergen.findMany({
+ where: { users: { some: { id: user.id } } },
+ });
+ return allergens.map((allergen) => allergen.name);
+ },
},
};
diff --git a/server/src/schema/typeDefs/user.graphql b/server/src/schema/typeDefs/user.graphql
index c60f6d9..766dae4 100644
--- a/server/src/schema/typeDefs/user.graphql
+++ b/server/src/schema/typeDefs/user.graphql
@@ -5,6 +5,7 @@ type User {
languagePref: String!
unitSystem: String!
isGuest: Boolean!
+ allergens: [String!]!
}
extend type Query {
@@ -19,4 +20,10 @@ extend type Mutation {
login(username: String!, password: String!): User!
logout: Boolean!
continueAsGuest: User!
+ # lagrer språk, måleenheter og allergier for brukere
+ updatePreferences(
+ languagePref: String!
+ unitSystem: String!
+ allergens: [String!]!
+ ): User!
}