Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions client/src/components/layout/StickyHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ export function StickyHeader({ visible, query }: StickyHeaderProps) {

<NavMenu compact />

<div className="ml-auto flex items-center gap-4">
<SearchBar initialQuery={query} inputId="sticky-search" />
<div className="ml-auto flex min-w-0 flex-1 items-center justify-end gap-4">
{/* søkefeltet krymper så knappene alltid får plass */}
<div className="min-w-0 max-w-sm flex-1">
<SearchBar initialQuery={query} inputId="sticky-search" />
</div>
<NewRecipeButton trigger={newRecipeButton} />
<UserLink />
</div>
Expand Down
65 changes: 54 additions & 11 deletions client/src/components/profile/PreferencesForm.tsx
Original file line number Diff line number Diff line change
@@ -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'];
Expand Down Expand Up @@ -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<string[]>([]);
const { user } = useCurrentUser();
const [language, setLanguage] = useState(user?.languagePref ?? 'nb');
const [units, setUnits] = useState(user?.unitSystem ?? 'metric');
const [allergies, setAllergies] = useState<string[]>(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 (
<section
aria-labelledby="preferanser-tittel"
Expand All @@ -79,7 +118,7 @@ export function PreferencesForm() {
legend="Språk"
name="language"
value={language}
onChange={setLanguage}
onChange={changeLanguage}
options={[
{ value: 'nb', label: 'Norsk' },
{ value: 'en', label: 'English' },
Expand All @@ -90,7 +129,7 @@ export function PreferencesForm() {
legend="Måleenheter"
name="units"
value={units}
onChange={setUnits}
onChange={changeUnits}
options={[
{ value: 'metric', label: 'Metrisk' },
{ value: 'imperial', label: 'Imperial' },
Expand Down Expand Up @@ -124,6 +163,10 @@ export function PreferencesForm() {
})}
</div>
</fieldset>
{/* sier om valgene ble lagret også til skjermleser */}
<p role="status" className="text-sm text-text/70">
{error ? 'Kunne ikke lagre. Prøv igjen.' : saved ? 'Lagret' : ''}
</p>
</section>
);
}
24 changes: 24 additions & 0 deletions client/src/graphql/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type BackendUser = {
languagePref: string;
unitSystem: string;
isGuest: boolean;
allergens: string[];
};

const USER_FIELDS = gql`
Expand All @@ -17,6 +18,7 @@ const USER_FIELDS = gql`
languagePref
unitSystem
isGuest
allergens
}
`;

Expand Down Expand Up @@ -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}
`;
1 change: 1 addition & 0 deletions client/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export type User = {
username?: string;
languagePref?: string;
unitSystem?: string;
allergens?: string[];
};
1 change: 1 addition & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions server/src/schema/resolvers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
},
},
};
7 changes: 7 additions & 0 deletions server/src/schema/typeDefs/user.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type User {
languagePref: String!
unitSystem: String!
isGuest: Boolean!
allergens: [String!]!
}

extend type Query {
Expand All @@ -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!
}